Home » excel » excel – Async VBA Function

excel – Async VBA Function

Posted by: admin April 23, 2020 Leave a comment

Questions:

I have a VBA function that is supposed to get some information from the user’s cell, make a POST request with that info, then print the response in the output cell.

It’s required that the user be able to make about 2000 requests at a time, so I thought to make the requests async to help improve performance.

As it stands right now, I have a function ConnectToAPI that makes the asynchronous request, then passes the response off to a callback function. The problem I’m having is that the data lives in the callback function, but I need it in the query function in order to return it.

Function Query(ID, quote, field)  
  Application.Volatile
  Query = ConnectToAPI(ID)
  Some logic with parsed data from callback
End Function


Function ConnectToAPI(ID)

    Dim Request As New WebRequest
    Dim Client As New WebClient
    Client.BaseUrl = "http://www.endpoint.com"

    Dim Wrapper As New WebAsyncWrapper
    Dim Wrapper.Client = Client
    Dim Body As New Dictionary
    Body.Add "ID", ID
    Set Request.Body = Body
    Request.Method = HttpPost

    ConnectToAPI = Wrapper.ExecuteAsync Request, "CallbackFunction"
End Function

Function CallbackFunction
    Callback = Parsed Data
End function

So ultimately in the query function, I want to write

Query = (Parsed Data From the Callback)

How can I pass the data from the callback back up to query?

It is important that the cell have the Query function in it. The data updates frequently, so we want clients to be able to calculate the workbook to get the newest data.

With what I currently have, my thought process is that the callback will pass the data back to ConnectToAPI, then that will be passed up to Query. However, my function returns 0 and I think this might be that the parsed data is not available once the function tries to return.

For reference, I am using the VBA-Web library
https://github.com/VBA-tools/VBA-Web

How to&Answers:

VBA-Web/src/WebAsyncWrapper.cls

enter image description here

WebAsyncWrapper.ExecuteAsync has an optional parameter: CallbackArgs. Use this parameter to pass back you an ID or a cell address.

ExecuteAsync has an example callback function that receives an Array of arguments.

enter image description here

Here is how you can get the information back to the function for processing.

Sub ConnectToAPI(ID As Variant, quote As Variant, field As Variant, CellAddress As Variant)

    Dim Request As New WebRequest
    Dim Client As New WebClient
    Client.BaseUrl = "http://www.endpoint.com"

    Dim Wrapper As New WebAsyncWrapper

    Dim Body As New Dictionary

    Body.Add "ID", ID
    Set Request.Body = Body
    Request.Method = HttpPost
    Set Wrapper.Client = Client
    Wrapper.ExecuteAsync Request, "Callback", Array(ID, CellAddress)
End Sub

Public Function Callback(Response As WebResponse, Args As Variant)
    Dim ID As Variant, CellAddress As Variant
    ID = Args(0)
    CellAddress = Args(1)
    With Worksheets("Web Requests")
        .Range(CellAddress).Value = Response
        .Range(CellAddress).Offset(0, 1).Value = ID
    End With
End Function

MSDN – Application.Volatile Method (Excel)

Marks a user-defined function as volatile. A volatile function must be recalculated whenever calculation occurs in any cells on the worksheet. A nonvolatile function is recalculated only when the input variables change. This method has no effect if it’s not inside a user-defined function used to calculate a worksheet cell.

I would not recommend trying to have a UDF that can be used as a worksheet function to return the web-requests. Application.Volatile will cause all 2000 queries to refresh every time a value is changed. When the first query updates all the other queries will refresh. This will cause an infinite loop and crash the application.

Function Query(ID, quote, field)  
  Application.Volatile
  Query = ConnectToAPI(ID)
  Some logic with parsed data from callback
End Function

Using the Worksheet_Change event would give the users the ability to update the information without the problems associated with Application.Volatile.

Private Sub Worksheet_Change(ByVal Target As Range)
    If Not Intersect(Target, Columns("A")) Is Nothing Then
        If Target.Count = 1 Then
            Debug.Print Target.Value, Target.Address
        End If
    End If
End Sub

Answer:

I ended up populating a dictionary with the response values from the API call, then recursively calling the query function in the callback.

Query checks if the response value is in the dictionary, if it is, then it returns it. If not, it connects to the api, the callback puts the value in the dictionary, and also calls the query function again.