How to correctly construct a loop to make sure the following promise call and the chained logger.log(res) runs synchronously through iterat
Here's how I do it with the standard Promise object.
// Given async function sayHi
function sayHi() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Hi');
resolve();
}, 3000);
});
}
// And an array of async functions to loop through
const asyncArray = [sayHi, sayHi, sayHi];
// We create the start of a promise chain
let chain = Promise.resolve();
// And append each function in the array to the promise chain
for (const func of asyncArray) {
chain = chain.then(func);
}
// Output:
// Hi
// Hi (After 3 seconds)
// Hi (After 3 more seconds)
Using the standard promise object, and having the promise return the results.
function promiseMap (data, f) {
const reducer = (promise, x) =>
promise.then(acc => f(x).then(y => acc.push(y) && acc))
return data.reduce(reducer, Promise.resolve([]))
}
var emails = []
function getUser(email) {
return db.getUser(email)
}
promiseMap(emails, getUser).then(emails => {
console.log(emails)
})
How about this one using BlueBird?
function fetchUserDetails(arr) {
return Promise.each(arr, function(email) {
return db.getUser(email).done(function(res) {
logger.log(res);
});
});
}
Bergi's suggested function is really nice:
var promiseWhile = Promise.method(function(condition, action) {
if (!condition()) return;
return action().then(promiseWhile.bind(null, condition, action));
});
Still I want to make a tiny addition, which makes sense, when using promises:
var promiseWhile = Promise.method(function(condition, action, lastValue) {
if (!condition()) return lastValue;
return action().then(promiseWhile.bind(null, condition, action));
});
This way the while loop can be embedded into a promise chain and resolves with lastValue (also if the action() is never run). See example:
var count = 10;
util.promiseWhile(
function condition() {
return count > 0;
},
function action() {
return new Promise(function(resolve, reject) {
count = count - 1;
resolve(count)
})
},
count)
Given
Required
Solution
let asyncFn = (item) => {
return new Promise((resolve, reject) => {
setTimeout( () => {console.log(item); resolve(true)}, 1000 )
})
}
// asyncFn('a')
// .then(()=>{return async('b')})
// .then(()=>{return async('c')})
// .then(()=>{return async('d')})
let a = ['a','b','c','d']
a.reduce((previous, current, index, array) => {
return previous // initiates the promise chain
.then(()=>{return asyncFn(array[index])}) //adds .then() promise for each item
}, Promise.resolve())
I don't think it guarantees the order of calling logger.log(res);
Actually, it does. That statement is executed before the resolve
call.
Any suggestions?
Lots. The most important is your use of the create-promise-manually antipattern - just do only
promiseWhile(…, function() {
return db.getUser(email)
.then(function(res) {
logger.log(res);
count++;
});
})…
Second, that while
function could be simplified a lot:
var promiseWhile = Promise.method(function(condition, action) {
if (!condition()) return;
return action().then(promiseWhile.bind(null, condition, action));
});
Third, I would not use a while
loop (with a closure variable) but a for
loop:
var promiseFor = Promise.method(function(condition, action, value) {
if (!condition(value)) return value;
return action(value).then(promiseFor.bind(null, condition, action));
});
promiseFor(function(count) {
return count < 10;
}, function(count) {
return db.getUser(email)
.then(function(res) {
logger.log(res);
return ++count;
});
}, 0).then(console.log.bind(console, 'all done'));