I hope you’ve had a chance to think about the code in the previous post for reading the value of a document property. This post will consider the problem of looping when async calls are involved, using that as the basis for the discussion.

VBA->JS: XML Parser
Before considering the problem of async loops, however, a quick word about using XML tools in JavaScript. While there are libraries (such as JSON) you can use, natively JavaScript can interact with the XML Parser provided by the host browser. In older code samples you may see code that checks whether the browser is Internet Explorer and makes a separate branch to handle it, using the Microsoft MSXML Parser. More recent versions of Internet Explorer – more specifically, those that can be used by a Web Add-in task pane – support the same Parser as other browsers, so no such branching is required.

If you try to use ActiveX to create a MSXML DOMDocument object you will get an error; the Web Add-in interface (automation server) does not support this.

Those who have used MSXML in VBA will therefore need to make some adjustments in their thinking – the princples of working with XML don’t change, just some of the syntax. This page presents a fairly good starting point as it indicates what version(s) of Internet Explorer use which syntax.

Async loops
It’s logical that, when there are a number of items on which the same actions should be performed, code should only be written once and re-used for each item. Typically, this is done using a loop or re-iterative process (calling the same function from within thet function). VBA and JavaScript loops were compared in a an earlier set of posts. This example uses a standard for loop to search for the specified document property in each namespace that could contain it.

So, it can happen to you like it did to me when preparing this sample about looking up built-in document properties. You start out with the concept, working with a single namespace, get all the details ironed out… And then put it in a loop and expect it will process for each namespace, right? Unfortunately, wrong <sigh>. And then the head scratching/banging and search for the answer begins…

There's nothing different or odd about the for syntax and JavaScript will let you write it for async the same as you do for sync. The result, however, will not be what you expect. It could be what you expect, part of the time, but then something totally weird will result and that’s when the worry sets in.

To see what I mean, try inserting some console.log statements at various places in the code – outside and in the async calls, run the code, then inspect the Visual Studio “JaveScript console” window. Generally, the synchronous calls will all be processed before the async calls finish (or sometimes even start). This means that the for loop iterations and any code following the loop will usually execute before the async processing takes place – NOT something VBA (or, indeed, classic .NET) developers are accustomed to dealing with!

So, how to get control? How can you make execution of what should follow the loop wait until the async calls have returned? What worked for me was the suggestion to increment a “counter” in every loop and test that against the total number of loop iterations (number of namespaces). The sample code where I found the idea had only one async call and I found putting the counter in the first async didn’t do the job – it needs to be in the last async call. The counter incrementation takes place before the function callback; the comparison test in the function callback:

function onGotXml(xmlResult) {
    var retVal = "";
    var sTemp
    var info = xmlResult.asyncContext;
--> counter++;
    if (xmlResult.status === Office.AsyncResultStatus.Succeeded) 
        retVal = xmlResult.value;
        sTemp = getDocProp(retVal, info[0]);
        var total = info[1];
        console.log(sTemp);
        if (breakLoop && sTemp!=='empty') {
            sPropVal = sTemp;
        }
        console.log(counter);
-->     if (counter === total) {
            done();
        }
   }
    else {
        console.log('Error:', xmlResult.error.message);
    }
}

Why? Remember that the function call-back of an async call only takes place after the async call returns. So everything inside the “pyramid” will execute in order. It’s important to realize that the individual loop iterations may not always finish in the same order. But the check whether all loops have finished will take place at the end of each loop. In this example, that means execution proceeds to the function done and NOT to any code following the for loop.

(Note that it makes no sense to try returning a value from a function callback as there’s no reasonable way to process it in the syncronous code – that has probably already executed by the point any value returns.)

This example has three variables at the top (but not global, as everything is within a function) level: counter, sPropValue and breakloop. They’re outside the functions because they need to maintain state during the calls to the named function, outside the “pyramid”. If sPropValue (the document property setting) were declared in getBuiltInDocProps and passed to onGotXml there would be no way to retain the value once it’s been found: it can’t be returned and the asyncronous loop can’t be broken to prevent subsequent loops from overwriting it.

This would not be a problem if only nested async functions were used, instead of a separate, named function because then all variables would be within the scope of getBuiltInDocProps.

The variable breakLoop stores the fact that the document property has been found so that subsequent loops don’t overwrite sPropValue.

Passing values to a function callback: asyncContext
Something you’ll find mentioned often in the documentation, but rarely demonstrated, is asyncContext. As the documentation says, this enables the developer to pass information to a function callback that will come through without being changed. Think of it like passing parameters By Val: since you can’t pass parameters in a normal manner to a function callback, you use this. You can see it in action in the call to the separate, named function callback onGotXml:

var asyncInfo = [sPropName, total];
.
.
.
cxp.getXmlAsync({ asyncContext: asyncInfo }, onGotXml);
.
.
.
function onGotXml(xmlResult) {
    var retVal = "";
    var sTemp
    var info = xmlResult.asyncContext;
    counter++;
    if (xmlResult.status === Office.AsyncResultStatus.Succeeded) {
        retVal = xmlResult.value;
        sTemp = getDocProp(retVal, info[0]);
        var total = info[1];

In this case, the document property name and the total number of loops are put in an array. In getXmlAsync the array is passed as an object to the function callback; note the label asyncContext, which is required! This is sent to onGotXml where it is accessed via the result provided by getXmlAsync. Again, this would not have been necessary if the function callback had been nested instead of being put in a separate, named function.

And if you’re wondering whether the three variables with scope outside the functions could have been passed as part of asyncContext, the answer is, no. The information is being passed “by value” and not “by reference” so there’s no way to retain changed state (the document property value, for example).

As you can see, using nested function callback pyramids have advantages when it comes to variable scope. Whether they outweigh the disadvantages of multiple levels of nesting/indentation is a matter of personal taste and the programming style required by the project you’re working on.

I hope these explanations have been clear enough to save you some of the headaches I’ve had! If not, feel free to open a discussion in the Comments.

Depending on when the Office 2016 APIs are released, it may be some time before I post again. Unless comments are posted on topics that warrant a longer discussion…

Leave a Reply