1

I need to run some promises on an array of items but I don't know the length of the array. How do I run these promises in sequence? This is how I do it asynchronously:

const arrayABC = [a, b, c.....x, y, z] // length unknown
const promises = arrayABC.map(function(item) {
   doSomething(item)
   }
return Promise.all(promises)

I need the promises to run through them one by one.

Di Ye
  • 739
  • 1
  • 8
  • 14

4 Answers4

1

Assuming I understand your setup correctly, this seems the most reasonable approach:

// return the promise chain from last element
return arrayABC.reduce(function (chain, item) {
  // bind item to first argument of function handle, replace `null` context as necessary
  return chain.then(doSomething.bind(null, item));
// start chain with promise of first item
}, doSomething(arrayABC.shift()));
Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • 4
    Reducing the array that is mutated in the same expression looks ugly. One might just pass a dummy resolved promise as an initial value instead. – zerkms Feb 23 '17 at 23:49
  • @zerkms that would waste event loops – Patrick Roberts Feb 23 '17 at 23:50
  • 4
    One event loop iteration vs controversial code... :-S – zerkms Feb 23 '17 at 23:52
  • How is it controversial? It's well-defined that the mutation occurs first. There's no such thing as undefined behavior in JavaScript. – Patrick Roberts Feb 24 '17 at 22:42
  • 2
    I did not say it's undefined behaviour. I said, using mutation (which is not free - at least in current v8 implementation it allocates a new array and fills it with old data. So your shift is O(N) in terms of both operations and memory) to save one event loop iteration is an arguable choice. It is usually not the best idea to mutate a collection you're iterating over. I'm not insisting though, it's up to everybody how to write their code. – zerkms Feb 25 '17 at 00:28
0

If you're down with using bluebird - http://bluebirdjs.com/docs/api/promise.each.html - you can use .each() to run them sequentially. Example taken from the documentation comments

const Promise = require('bluebird')

let queue = [];

queue.push(function() {
  return new Promise(function(resolve, reject) {
    // Some asynchronous task.
    console.log("Processing item 1...");
    setTimeout(resolve, 3000);
  });
});

queue.push(function() {
  return new Promise(function(resolve, reject) {
    // Some asynchronous task.
    console.log("Processing item 2...");
    setTimeout(resolve, 1000);
  });
});

Promise.each(queue, function(queue_item) {
  // *Now* we call the function. Since it returns a promise, the next iteration will not run until it resolves.
  return queue_item();
});
Jim Factor
  • 1,465
  • 1
  • 15
  • 24
-1

The following mapping is asynchronous and sequential, while your code is asynchronous and parallel.

function sequentialAsyncMap(array, fn) {
    var p = Promise.resolve();
    return Promise.all(array.map(
        (item, index, collection) => p = p.then(
            () => fn(item, index, collection)
        )
    ));
}

function delay(timeout, value) {
    return new Promise(resolve => {
        setTimeout(() => resolve(value), timeout);
    });
}

sequentialAsyncMap([1, 2, 3], i => {
    console.log(i);
    return delay(500, 2*i);
}).then(result => {
    console.log(result);
});
Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97
-1

If you need to access all the Promises results (like you can with Promise.all) - you can do the following:

Promise.series = (array, fn, thisArg) => {
    var p = Promise.resolve();
    return Promise.all(Array.from(array).map((...args) => 
        p = p
        .then(() => fn.apply(thisArg, args))
    ));
};

Then your code becomes

const arrayABC = [a, b, c.....x, y, z] // length unknown
return Promise.series(promises, doSomething);

The callback (in this case doSomething) will be passed the arguments item, index, array, just like Array#map, Array#forEach etc

thisArg is optional, and works just like how you would use it for Array#map, Array#forEach etc

An alternative Promise.series

Promise.series = (array, fn, thisArg) => 
    Array.from(array).reduce((promise, ...args) => 
        promise
        .then(results => 
            fn.apply(thisArg, args)
            .then(result => results.concat(result))
        ), Promise.resolve([])
    );

same result, but no need for Promise.all

Jaromanda X
  • 53,868
  • 5
  • 73
  • 87