erotavlas
erotavlas

Reputation: 4483

How to break out of a sequence of asynchronous operations in a loop?

Following this example

Dojo FAQ: How can I sequence asynchronous operations?

function doNext(previousValue) {
    var dfd = new Deferred();

    // perform some async logic; resolve the promise
    setTimeout(function () {
        var next = String.fromCharCode(previousValue.charCodeAt(previousValue.length - 1) + 1);
        dfd.resolve(previousValue + next);
    }, 50);

    return dfd.promise;
}

var promise = doNext('a');

for (var i = 0; i < 9; i++) {
    promise = promise.then(doNext);
}

promise.then(function (finalResult) {
    // 'doNext' will have been invoked 10 times, each
    // invocation only occurring after the previous one completed

    // 'finalResult' will be the value returned
    // by the last invocation of 'doNext': 'abcdefghijk'
    console.log(finalResult);
});

How do I break out of the loop - i.e. stop processing subsequent doNext function calls when doNext meets a certain criteria - for example stop when the next character is 'd' and return the computed value up to that point?

EDIT: so far I tried using deferred cancel() method, but it just kills the process, and returns nothing.

setTimeout(function () {
    var next = String.fromCharCode(previousValue.charCodeAt(previousValue.length - 1) + 1);
    if(previousValue + next == 'abc')
        dfd.cancel('abc');
    else
    dfd.resolve(previousValue + next);
}, 50);

Upvotes: 1

Views: 238

Answers (2)

Bergi
Bergi

Reputation: 664548

You should only use the reduce (or promise = promise.then(doNext)) loop approach when you always want to process all the items (or decide synchronously how many you need).

To loop an arbitrary number of times and break out at any step, recursion is the better approach:

function wait(t, v) {
    var dfd = new Deferred();
    // asynchronously resolve the promise
    setTimeout(function () {
        dfd.resolve(v);
    }, t);
    return dfd.promise;
}

function doNext(previousValue) {
    var next = String.fromCharCode(previousValue.charCodeAt(previousValue.length - 1) + 1);
    return wait(50, previousValue + next);
}

function loop(v, i) {
    if (i <= 0) return when(v);
    if (v == "abc") return when("abc");
    return doNext(v).then(function(r) {
        return loop(r, i-1);
    });
}

loop('a', 9).then(function (finalResult) {
    console.log(finalResult);
});

Upvotes: 1

T Kambi
T Kambi

Reputation: 1387

You could do it by check the value returned from the promise and decide whether to call the async request again or not. It is not like you add a break in the for loop. But the result will be what you desire.

All the 9 promise.then will be called but the doNext will not be called 9 times. Below is the snippet for the same.

for (var i = 0; i < 9; i++) {
    promise = promise.then(function(val){
        return val === "abcd" ? val : doNext(val);
    });
}

You might think this is not existing the loop. That is because the loop would have completed before the callback function is called. But, instead of calling the async function the callback function will simply return the value. which causes the loop to finish quickly. Below is a link to JSBin where I have increased the timeout and you will see that, initially it will take more time till the desired result is returned and then exits quickly.

https://jsbin.com/qiwesecufi/edit?js,console,output

Another, place you can do the checking, is within the doNext function itself.

function doNext(previousValue) {
    var dfd = new Deferred();

    if(previousValue === "abcd")
        return previousValue;

    // perform some async logic; resolve the promise
    setTimeout(function () {
        var next = String.fromCharCode(previousValue.charCodeAt(previousValue.length - 1) + 1);
        dfd.resolve(previousValue + next);
    }, 1000);

    return dfd.promise;
}

Hope this was helpful.

Upvotes: 1

Related Questions