Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript: Perform a chain of promises synchronously

I have a request promises that I need to perform over a loop, something like:

var ids = [1,2,3];
doPromise(1).then(function(){
  doPromise(2).then(function(){
    doPromise(3);
  }
})

The problem is I never know how many elements will be in the array, so I would need a dynamic pattern. Is it possible to mix the sync and async worlds, so that only one request is active at a moment (sequence being not important)?

like image 762
user776686 Avatar asked Dec 18 '22 22:12

user776686


1 Answers

A classic way to iterate over an array sequentually, calling some async operation on each array element is by using .reduce() and chaining to an initial promise as shown below:

The problem is I never know how many elements will be in the array, so I would need a dynamic pattern.

You can use chaining of promises to sequence them one after. For this, it is useful to use .reduce() to iterate the array since it offers the right type of iteration that keeps track of an accumulated value (a promise in this case) as one iterates the array. You could make almost any array iteration scheme work using extra variables, this just lines up well with .reduce():

var ids = [1,2,3,4,5,6,7];
ids.reduce(function(p, item) {
    return p.then(function() {
        return doPromise(item);
    });
}, Promise.resolve()).then(function(results) {
    // all done here with array of results
});

This passes a resolved promise to ids.reduce() as the head of the promise chain. Then, it does a .then() on that promise and returns a new promise for each item in the array (via the .reduce() callback). The final result of the .reduce() call will be a promise which, when resolved means the entire chain is done.

The key to understanding this is to remember that p.then() returns a new promise so we just keep calling .then() on each new promise and we return the promise from each operation in each .then() handler. This has the effect of chaining all the promises together into one sequential chain.

Is it possible to mix the sync and async worlds, so that only one request is active at a moment (sequence being not important)?

I'm not sure what you mean by "mix the sync and async worlds". The way to make sure that only one request is ever in-flight at a time is to sequence them to be one after the other so the next one starts only when the prior one finishes. This also happens to guarantee the execution order even though you say that isn't important in this case, but it is a by-product of making sure only one is in-flight at a time.

And, here's a working snippet:

function log(msg) {
    var d = document.createElement("div");
    d.textContent = msg;
    document.body.appendChild(d);
}

function doPromise(x) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            log(x);
            resolve(x);
        }, Math.floor(Math.random() * 1000));
    });
}

var ids = [1,2,3,4,5,6,7];
ids.reduce(function(p, item) {
    return p.then(function() {
        return doPromise(item);
    });
}, Promise.resolve()).then(function() {
    // all done here
    log("all done");
});
like image 147
jfriend00 Avatar answered Jan 09 '23 04:01

jfriend00