jjzjj

xml - VBA 如何捕获请求超时错误?

coder 2024-06-23 原文

我正在使用对象 MSXML2.ServerXMLHTTP60向网络服务发送请求;有了这个对象,我可以通过 加速数据加载异步 方法和避免锁定 Excel 屏幕(无响应)。但是,我在 webservice 响应很长时间时仍然有问题,超出 ServerXMLHTTP60 超时设置,请求功能是静默的,我无法捕获超时错误。在 another question , @osknows 建议使用 xmlhttp status = 408捕捉超时错误,但它对我不起作用。

我准备了一个测试文件,大家可以下载at here .按 Atl + F8 打开 VBA 源代码, 你会看到类模块 CXMLHTTPHandler ,我从 this guide 复制的

    If m_xmlHttp.readyState = 4 Then
        If m_xmlHttp.Status = 200 Then
            MsgBox m_xmlHttp.responseText
        ElseIf m_xmlHttp.Status = 408 Then 'Debug never run to here?
            MsgBox "Request timeout"
        Else
         'Error happened
        End If
    End If

VBA 如何捕获请求超时错误?

感谢您的帮助!

最佳答案

这里有几个并发症。

  • MSXML2.ServerXMLHTTP不公开 COM 可用事件。因此,使用 WithEvents 实例化对象并不容易。并附加到其 OnReadyStateChange事件。
    该事件在那里,但处理它的标准 VBA 方式不起作用。
  • 无法使用 VBA IDE 创建可以处理该事件的模块。
  • 您需要拨打 waitForResponse() 当您使用异步请求时(除了调用 setTimeouts() !)
  • 没有timeout事件。超时作为错误抛出。

  • 解决问题#1:

    通常,VBA 类模块(也适用于用户表单或工作表模块)允许您执行以下操作:

    Private WithEvents m_xhr As MSXML2.ServerXMLHTTP
    

    所以你可以定义一个这样的事件处理程序:

    Private Sub m_xhr_OnReadyStateChange()
      ' ...
    End Sub
    
    MSXML2.ServerXMLHTTP 并非如此.这样做将导致 Microsoft Visual Basic 编译错误:“对象不提供自动化事件”。

    显然,该事件没有导出供 COM 使用。有办法解决这个问题。
    onreadystatechange的签名阅读

    Property onreadystatechange As Object
    

    所以你可以分配一个对象。我们可以用 onreadystatechange 创建一个类模块方法和分配是这样的:

    m_xhr.onreadystatechange = eventHandlingObject
    

    但是,这不起作用。 onreadystatechange期望一个对象,每当事件触发时,都会调用对象本身,而不是我们定义的方法。 (对于 ServerXMLHTTP 实例,无法知道我们打算使用用户定义的 eventHandlingObject 的哪个方法作为事件处理程序)。

    我们需要一个可调用对象,即一个带有默认方法的对象(每个 COM 对象都可以有一个)。
    (例如: Collection 对象是可调用的,您可以说 myCollection("foo")myCollection.Item("foo") 的简写。)

    解决问题#2:

    我们需要一个具有默认属性的类模块。不幸的是,这些不能使用 VBA IDE 创建,但您可以使用文本编辑器创建它们。
  • 准备包含 onreadystatechange 的类模块VBA IDE 中的函数
  • 将其导出到 .cls通过右键单击文件
  • 在文本编辑器中打开它并在 onreadystatechange 下方添加以下行签名:Attribute OnReadyStateChange.VB_UserMemId = 0
  • 删除原始类模块并从文件中重新导入它。

  • 这会将修改后的方法标记为 Default .您可以在对象浏览器 (F2) 中看到一个小蓝点,它标志着默认方法:



    所以每次调用对象时,实际上是OnReadyStateChange方法被调用。

    解决问题 #3:

    只需拨打 waitForResponse()send() .

    m_xhr.Send
    m_xhr.waitForResponse timeout
    

    在超时的情况下:如果您没有调用此方法,则请求将永远不会返回。如果你这样做了,在 timeout 之后会抛出一个错误。毫秒。

    解决问题#4:

    我们需要使用 On Error为方便起见,捕获超时错误并将其转换为事件的处理程序。

    放在一起

    这是我编写的 VB 类模块,它包装和处理 MSXML2.ServerXMLHTTP目的。另存为 AjaxRequest.cls并将其导入到您的项目中:

    VERSION 1.0 CLASS
    BEGIN
      MultiUse = -1  'True
    END
    Attribute VB_Name = "AjaxRequest"
    Attribute VB_GlobalNameSpace = False
    Attribute VB_Creatable = False
    Attribute VB_PredeclaredId = False
    Attribute VB_Exposed = False
    Option Explicit
    
    Private m_xhr As MSXML2.ServerXMLHTTP
    Attribute m_xhr.VB_VarHelpID = -1
    Private m_isRunning As Boolean
    
    ' default timeouts. TIMEOUT_RECEIVE can be overridden in request
    Private Const TIMEOUT_RESOLVE As Long = 1000
    Private Const TIMEOUT_CONNECT As Long = 1000
    Private Const TIMEOUT_SEND As Long = 10000
    Private Const TIMEOUT_RECEIVE As Long = 30000
    
    Public Event Started()
    Public Event Stopped()
    Public Event Success(data As String, serverStatus As String)
    Public Event Error(data As String, serverStatus As String, xhr As MSXML2.ServerXMLHTTP)
    Public Event TimedOut(message As String)
    
    Private Enum ReadyState
      XHR_UNINITIALIZED = 0
      XHR_LOADING = 1
      XHR_LOADED = 2
      XHR_INTERACTIVE = 3
      XHR_COMPLETED = 4
    End Enum
    
    Public Sub Class_Terminate()
      Me.Cancel
    End Sub
    
    Public Property Get IsRunning() As Boolean
      IsRunning = m_isRunning
    End Property
    
    Public Sub Cancel()
      If m_isRunning Then
        m_xhr.abort
        m_isRunning = False
        RaiseEvent Stopped
      End If
      Set m_xhr = Nothing
    End Sub
    
    Public Sub HttpGet(url As String, Optional timeout As Long = TIMEOUT_RECEIVE)
      Send "GET", url, vbNullString, timeout
    End Sub
    
    Public Sub HttpPost(url As String, data As String, Optional timeout As Long = TIMEOUT_RECEIVE)
      Send "POST", url, data, timeout
    End Sub
    
    Private Sub Send(method As String, url As String, data As String, Optional timeout As Long)
      On Error GoTo HTTP_error
    
      If m_isRunning Then
        Me.Cancel
      End If
    
      RaiseEvent Started
    
      Set m_xhr = New MSXML2.ServerXMLHTTP60
    
      m_xhr.OnReadyStateChange = Me
      m_xhr.setTimeouts TIMEOUT_RESOLVE, TIMEOUT_CONNECT, TIMEOUT_SEND, timeout
    
      m_isRunning = True
      m_xhr.Open method, url, True
      m_xhr.Send data
      m_xhr.waitForResponse timeout
    
      Exit Sub
    
    HTTP_error:
      If Err.Number = &H80072EE2 Then
        Err.Clear
        Me.Cancel
        RaiseEvent TimedOut("Request timed out after " & timeout & "ms.")
        Resume Next
      Else
        Err.Raise Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext
      End If
    End Sub
    
    ' Note: the default method must be public or it won't be recognized
    Public Sub OnReadyStateChange()
    Attribute OnReadyStateChange.VB_UserMemId = 0
      If m_xhr.ReadyState = ReadyState.XHR_COMPLETED Then
        m_isRunning = False
        RaiseEvent Stopped
    
        ' TODO implement 301/302 redirect support
    
        If m_xhr.Status >= 200 And m_xhr.Status < 300 Then
          RaiseEvent Success(m_xhr.responseText, m_xhr.Status)
        Else
          RaiseEvent Error(m_xhr.responseText, m_xhr.Status, m_xhr)
        End If
      End If
    End Sub
    

    注意行 m_xhr.OnReadyStateChange = Me ,它将 AjaxRequest 实例本身分配为事件处理程序,这可以通过标记 OnReadyStateChange() 来实现。作为默认方法。

    请注意 如果您更改 OnReadyStateChange()您需要再次执行导出/修改/重新导入例程,因为 VBA IDE 不保存“默认方法”属性。

    该类公开了以下接口(interface)
  • 方法:
  • HttpGet(url As String, [timeout As Long])
  • HttpPost(url As String, data As String, [timeout As Long])
  • Cancel()
  • 特性
  • IsRunning As Boolean
  • 事件
  • Started()
  • Stopped()
  • Success(data As String, serverStatus As String)
  • Error(data As String, serverStatus As String, xhr As MSXML2.ServerXMLHTTP)
  • TimedOut(message As String)

  • 在另一个类模块中使用它,例如在用户表单中,使用 WithEvents :

    Option Explicit
    
    Private WithEvents ajax As AjaxRequest
    
    Private Sub UserForm_Initialize()
      Set ajax = New AjaxRequest
    End Sub
    
    Private Sub CommandButton1_Click()
      Me.TextBox2.Value = ""
    
      If ajax.IsRunning Then
        ajax.Cancel
      Else
        ajax.HttpGet Me.TextBox1.Value, 1000
      End If
    End Sub
    
    Private Sub ajax_Started()
      Me.Label1.Caption = "Running" & Chr(133)
      Me.CommandButton1.Caption = "Cancel"
    End Sub
    
    Private Sub ajax_Stopped()
      Me.Label1.Caption = "Done."
      Me.CommandButton1.Caption = "Send Request"
    End Sub
    
    Private Sub ajax_TimedOut(message As String)
      Me.Label1.Caption = message
    End Sub
    
    Private Sub ajax_Success(data As String, serverStatus As String)
      Me.TextBox2.Value = serverStatus & vbNewLine & data
    End Sub
    
    Private Sub ajax_Error(data As String, serverStatus As String, xhr As MSXML2.ServerXMLHTTP)
      Me.TextBox2.Value = serverStatus
    End Sub
    

    进行您认为合适的增强。 AjaxRequest类只是回答这个问题的副产品。

    关于xml - VBA 如何捕获请求超时错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11607677/

    有关xml - VBA 如何捕获请求超时错误?的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

      给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

    5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    6. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

      大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

    7. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

      我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

    8. ruby - 如何指定 Rack 处理程序 - 2

      Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

    9. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

      在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

    10. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

      我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

    随机推荐