for (let i = 0; i < 10; i++) {
const promise = new Promise((resolve, reject) => {
const timeout = Math.random() * 1000;
setTimeout(() => {
console.log(i);
}, timeout);
});
// TODO: Chain this promise to the previous one (maybe without having it running?)
}
The above will give the following random output:
6
9
4
8
5
1
7
2
3
0
The task is simple: Make sure each promise runs only after the other one (.then()
).
For some reason, I couldn't find a way to do it.
I tried generator functions (yield
), tried simple functions that return a promise, but at the end of the day it always comes down to the same problem: The loop is synchronous.
With async I'd simply use async.series()
.
How do you solve it?
This question is related to
javascript
es6-promise
Based on the excellent answer by trincot, I wrote a reusable function that accepts a handler to run over each item in an array. The function itself returns a promise that allows you to wait until the loop has finished and the handler function that you pass may also return a promise.
It took me some time to get it right, but I believe the following code will be usable in a lot of promise-looping situations.
Copy-paste ready code:
// SEE https://stackoverflow.com/a/46295049/286685
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
To use it, call it with the array to loop over as the first argument and the handler function as the second. Do not pass parameters for the third, fourth and fifth arguments, they are used internally.
const loop = (arr, fn, busy, err, i=0) => {_x000D_
const body = (ok,er) => {_x000D_
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}_x000D_
catch(e) {er(e)}_x000D_
}_x000D_
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)_x000D_
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()_x000D_
return busy ? run(busy,err) : new Promise(run)_x000D_
}_x000D_
_x000D_
const items = ['one', 'two', 'three']_x000D_
_x000D_
loop(items, item => {_x000D_
console.info(item)_x000D_
})_x000D_
.then(() => console.info('Done!'))
_x000D_
Let's look at the handler function, nested loops and error handling.
The handler gets passed 3 arguments. The current item, the index of the current item and the complete array being looped over. If the handler function needs to do async work, it can return a promise and the loop function will wait for the promise to resolve before starting the next iteration. You can nest loop invocations and all works as expected.
const loop = (arr, fn, busy, err, i=0) => {_x000D_
const body = (ok,er) => {_x000D_
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}_x000D_
catch(e) {er(e)}_x000D_
}_x000D_
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)_x000D_
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()_x000D_
return busy ? run(busy,err) : new Promise(run)_x000D_
}_x000D_
_x000D_
const tests = [_x000D_
[],_x000D_
['one', 'two'],_x000D_
['A', 'B', 'C']_x000D_
]_x000D_
_x000D_
loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {_x000D_
console.info('Performing test ' + idx)_x000D_
return loop(test, (testCase) => {_x000D_
console.info(testCase)_x000D_
})_x000D_
.then(testNext)_x000D_
.catch(testFailed)_x000D_
}))_x000D_
.then(() => console.info('All tests done'))
_x000D_
Many promise-looping examples I looked at break down when an exception occurs. Getting this function to do the right thing was pretty tricky, but as far as I can tell it is working now. Make sure to add a catch handler to any inner loops and invoke the rejection function when it happens. E.g.:
const loop = (arr, fn, busy, err, i=0) => {_x000D_
const body = (ok,er) => {_x000D_
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}_x000D_
catch(e) {er(e)}_x000D_
}_x000D_
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)_x000D_
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()_x000D_
return busy ? run(busy,err) : new Promise(run)_x000D_
}_x000D_
_x000D_
const tests = [_x000D_
[],_x000D_
['one', 'two'],_x000D_
['A', 'B', 'C']_x000D_
]_x000D_
_x000D_
loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {_x000D_
console.info('Performing test ' + idx)_x000D_
loop(test, (testCase) => {_x000D_
if (idx == 2) throw new Error()_x000D_
console.info(testCase)_x000D_
})_x000D_
.then(testNext)_x000D_
.catch(testFailed) // <--- DON'T FORGET!!_x000D_
}))_x000D_
.then(() => console.error('Oops, test should have failed'))_x000D_
.catch(e => console.info('Succesfully caught error: ', e))_x000D_
.then(() => console.info('All tests done'))
_x000D_
Since writing this answer, I turned the above code in an NPM package.
npm install --save for-async
var forAsync = require('for-async'); // Common JS, or
import forAsync from 'for-async';
var arr = ['some', 'cool', 'array'];
forAsync(arr, function(item, idx){
return new Promise(function(resolve){
setTimeout(function(){
console.info(item, idx);
// Logs 3 lines: `some 0`, `cool 1`, `array 2`
resolve(); // <-- signals that this iteration is complete
}, 25); // delay 25 ms to make async
})
})
See the package readme for more details.
You can use async/await
for this. I would explain more, but there's nothing really to it. It's just a regular for
loop but I added the await
keyword before the construction of your Promise
What I like about this is your Promise can resolve a normal value instead of having a side effect like your code (or other answers here) include. This gives you powers like in The Legend of Zelda: A Link to the Past where you can affect things in both the Light World and the Dark World – ie, you can easily work with data before/after the Promised data is available without having to resort to deeply nested functions, other unwieldy control structures, or stupid IIFEs.
// where DarkWorld is in the scary, unknown future
// where LightWorld is the world we saved from Ganondorf
LightWorld ... await DarkWorld
So here's what that will look like ...
const someProcedure = async n =>_x000D_
{_x000D_
for (let i = 0; i < n; i++) {_x000D_
const t = Math.random() * 1000_x000D_
const x = await new Promise(r => setTimeout(r, t, i))_x000D_
console.log (i, x)_x000D_
}_x000D_
return 'done'_x000D_
}_x000D_
_x000D_
someProcedure(10).then(x => console.log(x)) // => Promise_x000D_
// 0 0_x000D_
// 1 1_x000D_
// 2 2_x000D_
// 3 3_x000D_
// 4 4_x000D_
// 5 5_x000D_
// 6 6_x000D_
// 7 7_x000D_
// 8 8_x000D_
// 9 9_x000D_
// done
_x000D_
See how we don't have to deal with that bothersome .then
call within our procedure? And async
keyword will automatically ensure that a Promise
is returned, so we can chain a .then
call on the returned value. This sets us up for great success: run the sequence of n
Promises, then do something important – like display a success/error message.
here's my 2 cents worth:
forpromise()
function forpromise(lo, hi, st, res, fn) {_x000D_
if (typeof res === 'function') {_x000D_
fn = res;_x000D_
res = undefined;_x000D_
}_x000D_
if (typeof hi === 'function') {_x000D_
fn = hi;_x000D_
hi = lo;_x000D_
lo = 0;_x000D_
st = 1;_x000D_
}_x000D_
if (typeof st === 'function') {_x000D_
fn = st;_x000D_
st = 1;_x000D_
}_x000D_
return new Promise(function(resolve, reject) {_x000D_
_x000D_
(function loop(i) {_x000D_
if (i >= hi) return resolve(res);_x000D_
const promise = new Promise(function(nxt, brk) {_x000D_
try {_x000D_
fn(i, nxt, brk);_x000D_
} catch (ouch) {_x000D_
return reject(ouch);_x000D_
}_x000D_
});_x000D_
promise._x000D_
catch (function(brkres) {_x000D_
hi = lo - st;_x000D_
resolve(brkres)_x000D_
}).then(function(el) {_x000D_
if (res) res.push(el);_x000D_
loop(i + st)_x000D_
});_x000D_
})(lo);_x000D_
_x000D_
});_x000D_
}_x000D_
_x000D_
_x000D_
//no result returned, just loop from 0 thru 9_x000D_
forpromise(0, 10, function(i, next) {_x000D_
console.log("iterating:", i);_x000D_
next();_x000D_
}).then(function() {_x000D_
_x000D_
_x000D_
console.log("test result 1", arguments);_x000D_
_x000D_
//shortform:no result returned, just loop from 0 thru 4_x000D_
forpromise(5, function(i, next) {_x000D_
console.log("counting:", i);_x000D_
next();_x000D_
}).then(function() {_x000D_
_x000D_
console.log("test result 2", arguments);_x000D_
_x000D_
_x000D_
_x000D_
//collect result array, even numbers only_x000D_
forpromise(0, 10, 2, [], function(i, collect) {_x000D_
console.log("adding item:", i);_x000D_
collect("result-" + i);_x000D_
}).then(function() {_x000D_
_x000D_
console.log("test result 3", arguments);_x000D_
_x000D_
//collect results, even numbers, break loop early with different result_x000D_
forpromise(0, 10, 2, [], function(i, collect, break_) {_x000D_
console.log("adding item:", i);_x000D_
if (i === 8) return break_("ending early");_x000D_
collect("result-" + i);_x000D_
}).then(function() {_x000D_
_x000D_
console.log("test result 4", arguments);_x000D_
_x000D_
// collect results, but break loop on exception thrown, which we catch_x000D_
forpromise(0, 10, 2, [], function(i, collect, break_) {_x000D_
console.log("adding item:", i);_x000D_
if (i === 4) throw new Error("failure inside loop");_x000D_
collect("result-" + i);_x000D_
}).then(function() {_x000D_
_x000D_
console.log("test result 5", arguments);_x000D_
_x000D_
})._x000D_
catch (function(err) {_x000D_
_x000D_
console.log("caught in test 5:[Error ", err.message, "]");_x000D_
_x000D_
});_x000D_
_x000D_
});_x000D_
_x000D_
});_x000D_
_x000D_
_x000D_
});_x000D_
_x000D_
_x000D_
_x000D_
});
_x000D_
If you are limited to ES6, the best option is Promise all. Promise.all(array)
also returns an array of promises after successfully executing all the promises in array
argument.
Suppose, if you want to update many student records in the database, the following code demonstrates the concept of Promise.all in such case-
let promises = students.map((student, index) => {
//where students is a db object
student.rollNo = index + 1;
student.city = 'City Name';
//Update whatever information on student you want
return student.save();
});
Promise.all(promises).then(() => {
//All the save queries will be executed when .then is executed
//You can do further operations here after as all update operations are completed now
});
Map is just an example method for loop. You can also use for
or forin
or forEach
loop. So the concept is pretty simple, start the loop in which you want to do bulk async operations. Push every such async operation statement in an array declared outside the scope of that loop. After the loop completes, execute the Promise all statement with the prepared array of such queries/promises as argument.
The basic concept is that the javascript loop is synchronous whereas database call is async and we use push method in loop that is also sync. So, the problem of asynchronous behavior doesn't occur inside the loop.
Source: Stackoverflow.com