How to Execute Promises Sequentially, Passing the Parameters from an Array

How to execute promises sequentially, passing the parameters from an array?

You can make the repeated application of .then into a fold pretty neatly if you’re okay with creating as many promises as array elements as is the case in the question:

myArray.reduce(
(p, x) =>
p.then(() => myPromise(x)),
Promise.resolve()
)

but given support, an async function is a better choice. It’s nicely readable and has O(1) instead of O(n) memory overhead.

const forEachSeries = async (iterable, action) => {
for (const x of iterable) {
await action(x)
}
}

forEachSeries(myArray, myPromise)

If you want to collect the return values as an array, that’s:

const mapSeries = async (iterable, fn) => {
const results = []

for (const x of iterable) {
results.push(await fn(x))
}

return results
}

or, without async function support,

const mapSeries = (iterable, fn) => {
const iterator = iterable[Symbol.iterator]()
const results = []
const go = () => {
const {value, done} = iterator.next()

if (done) {
return results
}

return fn(value).then(mapped => {
results.push(mapped)
return go()
})
}

return Promise.resolve().then(go)
}

Runnable snippet:

const myArray = [1, 2, 3, 4, 5, 6]

const sleep = ms =>
new Promise(res => {
setTimeout(res, ms)
})

const myPromise = num =>
sleep(500).then(() => {
console.log('done: ' + num)
})

const forEachSeries = async (iterable, action) => {
for (const x of iterable) {
await action(x)
}
}

forEachSeries(myArray, myPromise)
.then(() => {
console.log('all done!')
})

JavaScript Promises: Executing Promises Sequentially

The executeSequentially method returns all the Promises one after each other. It happens to iterate over promiseFactory, but it could be written as:

function executeSequentially(promiseFactories) {
return doFirstThing()
.then(() => doSecondThing())
.then(doThirdThing() );
}

It is just the same. We are basically returning a Promise.

Now, however, we want to iterate over a collection of promises.

When iterating, we need to attach the current Promise to the previous with a then. But the forEach does not expose the next Promise -or the previous- in every iteration. And yet we still need it in order to keep chaining Promises one by one. Hence, the result 'hack':

function executeSequentially(promiseFactories) {
var result = Promise.resolve(); /*We need a thing that keeps yelling
the previous promise in every iteration, so we can keep chaining.
This 'result' var is that thing. This is keeping a Promise in every
iteration that resolves when all the previous promises resolve
sequentially. Since we don't have a Promise in the array
previous to the first one, we fabricate one out of 'thin air'
with Promise.resolve() */
promiseFactories.forEach(function (promiseFactory) {
result = result.then(promiseFactory); /* Here result is update
with a new Promise, with is the result of chaining `result`
with the current one. Since `result` already had all the previous ones,
at the end, `result` will be a Promise that depends upon all the
Promises resolution.*/
});
return result;
}

Now, there's also a syntax quirk that maybe is puzzling you:

result = result.then(promiseFactory);

This line is pretty much the same as the following:

result = result.then(resolvedValue => promiseFactory(resolvedValue));

Please if somebody can help me understand how calling the promiseFactory method inside the 'then' method of the empty promise makes it execute sequentially, like so. Or is it because of the forEach loop ?

First thing first, promiseFactory is a pretty bad name there. The method should be better written as follows:

function executeSequentially(promises) {
var result = Promise.resolve(); // this is the most problematic line
promises.forEach(function (currentPromise) {
result = result.then(currentPromise);// what is happening here ?
});
return result;
}

So:

how calling the currentPromise method inside the 'then' method of the empty promise makes it execute sequentially?

It makes execute sequentially because when you attach a Promise to another by then, it executes sequentially. Is a then thing, it is not at all related to the fact that we are iterating over Promises. With plain Promises outside an iteration it works pretty much the same:

Promise.resolve() // fake Promises that resolves instanly
.then(fetchUsersFromDatabase) // a function that returns a Promise and takes
// like 1 second. It won't be called until the first one resolves
.then(processUsersData) // another function that takes input from the first, and
// do a lot of complex and asynchronous computations with data from the previous promise.
// it won't be called until `fetchUsersFromDatabase()` resolves, that's what
// `then()` does.
.then(sendDataToClient); // another function that will never be called until
// `processUsersData()` resolves

How to execute promises in order and return all results

Try using an async function and iterating over the promises in a for...of loop:

Resolve promises one after another (i.e. in sequence)?

How can I execute array of promises in sequential order?

If you already have them in an array then they are already executing. If you have a promise then it's already executing. This is not a concern of promises (I.E they are not like C# Tasks in that regard with .Start() method). .all doesn't execute anything
it just returns a promise.

If you have an array of promise returning functions:

var tasks = [fn1, fn2, fn3...];

tasks.reduce(function(cur, next) {
return cur.then(next);
}, RSVP.resolve()).then(function() {
//all executed
});

Or values:

var idsToDelete = [1,2,3];

idsToDelete.reduce(function(cur, next) {
return cur.then(function() {
return http.post("/delete.php?id=" + next);
});
}, RSVP.resolve()).then(function() {
//all executed
});

Execute an Array of promises sequentially without using async/await

Here could be a possible option:

let p = Promise.resolve([]);
promisesArray.forEach(q => {
p = p.then(responses => {
//based on the nature of each q, to start execution
//use either q().then() or q.then()
return q().then(response => {
//Any further logic can be here.
console.log(response);
return responses.concat([response]);
})
})
})

p.then(responses => {
// here you have all of the responses.
})

Resolve promises one after another (i.e. in sequence)?

Update 2017: I would use an async function if the environment supports it:

async function readFiles(files) {
for(const file of files) {
await readFile(file);
}
};

If you'd like, you can defer reading the files until you need them using an async generator (if your environment supports it):

async function* readFiles(files) {
for(const file of files) {
yield await readFile(file);
}
};

Update: In second thought - I might use a for loop instead:

var readFiles = function(files) {
var p = Promise.resolve(); // Q() in q

files.forEach(file =>
p = p.then(() => readFile(file));
);
return p;
};

Or more compactly, with reduce:

var readFiles = function(files) {
return files.reduce((p, file) => {
return p.then(() => readFile(file));
}, Promise.resolve()); // initial
};

In other promise libraries (like when and Bluebird) you have utility methods for this.

For example, Bluebird would be:

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));

var readAll = Promise.resolve(files).map(fs.readFileAsync,{concurrency: 1 });
// if the order matters, you can use Promise.each instead and omit concurrency param

readAll.then(function(allFileContents){
// do stuff to read files.
});

Although there is really no reason not to use async await today.

How to call a promise on every value of an array sequentially?

Using the Array#reduce approach:

let array = [1, 2, 3, 4];
array.reduce((p, value) => {
return p.then(() => MyPromiseFunction(value));
}, Promise.resolve());

Calling asynchronous functions from an array

Without async/await you could reduce the fns array:

function promisesInSeries(fns) {
return fns.reduce((promise, fn) => promise.then(fn), Promise.resolve());
}

The first fn will receive the value undefined, after that each return value is passed to the next function.

If you want to have some initial value, you can provide it by changing Promise.resolve() to Promise.resolve(initialData). Where initialData could be a static value or a value passed through function parameters.



Related Topics



Leave a reply



Submit