locked
Chaining of work using promises

    Question

  • Hi

    I use an array to loop through an async function which returns a promise, this way each loop can fully complete before starting the next loop.

    The issue im getting is that the .done function which follows this always gets called before my loop is finished. Can someone tell me how I can get my code to wait for the .then function to complete before starting the .done function.

    Thanks in advance.

    .then(
    function (arrServiceID) {
             arrServiceID = [892816, 891452];
                    var result =
                            arrServiceID.reduce(
                                function (p, id) {
                                    return p.then(function () {
                                        return roviObj.buildChannelListAsync(id);                                    
                                    });
                                },
                                WinJS.Promise.wrap()
                            )                        
                        }
                    )
                    .done(
                        function () {
                            console.log('The final channelList length is '+channelList.length);
                            console.log('The final channelServicesList length is ' +  channelServicesList.length);
                        }
                    );

    Monday, June 10, 2013 9:28 PM

Answers

  • Ah, yes, I see. You want the operations to run sequentially (which delivers their results sequentially, of course). In that case your original code looks correct, so I'm not sure why the completed handler for the end result is being called right away.

    I added similar code (below) to a suite of tests I have for promises.

       function runScenario() {
            var args = [1000000, 500000, 300000, 150000, 50000, 10000];
            
            var result = args.reduce(function (p, id) {
                return p.then(function (r) {
                    console.log("operation completed with results = " + r);
                    return calculateIntegerSum(id, 100);
                });
            }, WinJS.Promise.as("(initial dummy value)"));
    
            result.done(function (r) {
                console.log("Final promise complete with results = " + r);
            }); 
        }
    
        function calculateIntegerSum(max, step) {
            if (max < 1 || step < 1) {
                var err = new WinJS.ErrorFromName("calculateIntegerSum (scenario 8)", "max and step must be 1 or greater");
                return WinJS.Promise.wrapError(err);
            }
    
            var _cancel = false;
    
            //The WinJS.Promise constructor's argument is a function that receives 
            //dispatchers for completed, error, and progress cases.
            return new WinJS.Promise(function (completeDispatch, errorDispatch, progressDispatch) {
                var sum = 0;
    
                function iterate(args) {
                    for (var i = args.start; i < args.end; i++) {
                        sum += i;
                    };
    
                    //If for some reason there was an error, create the error with WinJS.ErrorFromName
                    //and pass to errorDispatch
                    if (false /* replace with any necessary error check -- we don’t have any here */) {
                        errorDispatch(new WinJS.ErrorFromName("calculateIntegerSum (scenario 8)", "error occurred"));
                    }
    
                    if (i >= max) {
                        //Complete--dispatch results to completed handlers
                        completeDispatch(sum);
                    } else {
                        //Dispatch intermediate results to progress handlers
                        progressDispatch(sum);
    
                        //Interrupt the operation if canceled
                        if (!_cancel) {
                            setImmediate(iterate, { start: args.end, end: Math.min(args.end + step, max) });
                        }
                    }
                }
    
                setImmediate(iterate, { start: 0, end: Math.min(step, max) });
            },
            //Cancellation function 
            function () {
                _cancel = true;
            });
        }

    It behaves as you are expecting, with the async operations being started after the results from the previous one are delivered.

    I wonder, then, how your buildChannelListAsync is constructed. Perhaps there's an issue in that?

    P.S. Note that WinJS.Promise.wrap is considered deprecated in favor of WinJS.Promise.as, as the latter behaves better if you give it an existing promise.

    I also included the link to my promises blog post in my earlier reply.

    Tuesday, June 11, 2013 6:08 PM

All replies

  • The trick to use here is to return a WinJS.Promise.join between the current operation's promise and the join of all the previous ones. Here's the code pattern that achieves that, where list will be your arrServiceID and doOperationAsync will be your roviObj.buildChannelListAsync:

    list.reduce(function callback (prev, item, i) {
        var opPromise = doOperationAsync(item);
        var join = WinJS.Promise.join({ prev: prev, result: opPromise});
    
        return join.then(function completed (v) {
            console.log(i + ", item: " + item+ ", " + v.result);
        });
    })

    The return value from reduce here should be the final join, so a completed handler you pass to .done on that one should complete only when all the others are complete. Note that the console output here is just for debugging; you can eliminate it in which case you can just call .then() with no args, as what we're really after here is the promise from join.then().

    Kraig
    Author,Programming Windows 8 Apps with HTML, CSS, and JavaScript, a free ebook from Microsoft Press

    P.S. I have a post for the Windows 8 developer blog called All About Promises, wherein I explain this piece of code in more detail.


    Monday, June 10, 2013 10:27 PM
  • Hi Kraig

    Thanks for the reply.

    My buildChannelListAsynch() method gets a feed and does a load of checks before returning a promise. I need each call to buildChannelListAsynch to fully complete before looping to the next item in the array.

    The code above sets off buildChannelListAsych for all values in the list before the first item had completed.

    Hope this makes sense.

    Jas

    Tuesday, June 11, 2013 7:42 AM
  • Ah, yes, I see. You want the operations to run sequentially (which delivers their results sequentially, of course). In that case your original code looks correct, so I'm not sure why the completed handler for the end result is being called right away.

    I added similar code (below) to a suite of tests I have for promises.

       function runScenario() {
            var args = [1000000, 500000, 300000, 150000, 50000, 10000];
            
            var result = args.reduce(function (p, id) {
                return p.then(function (r) {
                    console.log("operation completed with results = " + r);
                    return calculateIntegerSum(id, 100);
                });
            }, WinJS.Promise.as("(initial dummy value)"));
    
            result.done(function (r) {
                console.log("Final promise complete with results = " + r);
            }); 
        }
    
        function calculateIntegerSum(max, step) {
            if (max < 1 || step < 1) {
                var err = new WinJS.ErrorFromName("calculateIntegerSum (scenario 8)", "max and step must be 1 or greater");
                return WinJS.Promise.wrapError(err);
            }
    
            var _cancel = false;
    
            //The WinJS.Promise constructor's argument is a function that receives 
            //dispatchers for completed, error, and progress cases.
            return new WinJS.Promise(function (completeDispatch, errorDispatch, progressDispatch) {
                var sum = 0;
    
                function iterate(args) {
                    for (var i = args.start; i < args.end; i++) {
                        sum += i;
                    };
    
                    //If for some reason there was an error, create the error with WinJS.ErrorFromName
                    //and pass to errorDispatch
                    if (false /* replace with any necessary error check -- we don’t have any here */) {
                        errorDispatch(new WinJS.ErrorFromName("calculateIntegerSum (scenario 8)", "error occurred"));
                    }
    
                    if (i >= max) {
                        //Complete--dispatch results to completed handlers
                        completeDispatch(sum);
                    } else {
                        //Dispatch intermediate results to progress handlers
                        progressDispatch(sum);
    
                        //Interrupt the operation if canceled
                        if (!_cancel) {
                            setImmediate(iterate, { start: args.end, end: Math.min(args.end + step, max) });
                        }
                    }
                }
    
                setImmediate(iterate, { start: 0, end: Math.min(step, max) });
            },
            //Cancellation function 
            function () {
                _cancel = true;
            });
        }

    It behaves as you are expecting, with the async operations being started after the results from the previous one are delivered.

    I wonder, then, how your buildChannelListAsync is constructed. Perhaps there's an issue in that?

    P.S. Note that WinJS.Promise.wrap is considered deprecated in favor of WinJS.Promise.as, as the latter behaves better if you give it an existing promise.

    I also included the link to my promises blog post in my earlier reply.

    Tuesday, June 11, 2013 6:08 PM