Reputation: 783
I'm trying to use a function with callback in forEach
loop.
I need to wait for execution to complete before moving on to next step.
Here's my code:
const arr = '6,7,7,8,8,5,3,5,1'
const id = arr.split(',');
const length = id.length;
id.forEach( (x, index) => {
(function FnWithCallback () {
setTimeout(() => { console.log(x) }, 5000);
})();
});
console.log('done');
I came up with a hack:
const arr = '6,7,7,8,8,5,3,5,1'
const id = arr.split(',');
const length = id.length;
const fn = () => {
return new Promise (resolve => {
id.forEach( (id, index) => {
setTimeout(() => {console.log(id)}, 3000);
if(index === (length - 1))
resolve();
})
})
}
fn().then(()=> {
console.log('done');
})
But the hack seems to be broken.
Can I have a real solution to this? A NPM package would be really helpful.
Note: I had a look at async.js. I'm not sure if that is something I want since I'm trying to avoid callback hell.
Upvotes: 2
Views: 577
Reputation: 5267
You can try this approach, where promisefy
will receive any type of value as the argument to be resolved after.
const IDs = '6,7,7,8,8,5,3,5,1'.split(',');
// simulates an async response after 1s
const promisefy = (value) => new Promise((resolve) => {
setTimeout(() => {
resolve(value);
}, 1000);
});
// stores all async responses
const responses = IDs.map((id, index) => {
return promisefy(id);
});
// executes sequentially all responses
responses.forEach((resp, index) => {
resp.then((value) => console.log(`id: ${value}, index: ${index}`));
});
Or using Array reduce()
// stores all async responses
const responses2 = IDs.map((id) => promisefy(id));
// executes sequentially all responses
responses2
.reduce((_, resp, index) => {
return resp.then((value) => console.log(`id: ${value}, index: ${index}`));
}, null)
.then(() => console.log('done'));
Upvotes: 0
Reputation: 13983
You can chain promises using Array.prototype.reduce()
starting with an initial resolved promise. You will have to turn your callback into a promise so you can chain them:
const arr = '6,7,7,8,8,5,3,5,1'
const ids = arr.split(',');
const length = ids.length;
const timeout = (fn, ms) => new Promise(res => {
setTimeout(() => { fn(); res(); }, ms);
});
ids.reduce((previousPromise, id) => {
return previousPromise.then(() => {
return timeout(() => console.log(id), 200);
});
}, Promise.resolve());
console.log('done');
Or using async/await:
const arr = '6,7,7,8,8,5,3,5,1'
const ids = arr.split(',');
const length = ids.length;
const timeout = (fn, ms) => new Promise(res => {
setTimeout(() => { fn(); res(); }, ms);
});
ids.reduce(async (previousPromise, id) => {
await previousPromise;
return timeout(() => console.log(id), 200);
}, Promise.resolve());
console.log('done');
Upvotes: 1
Reputation: 138537
If you want to make sure that the async actions aren't running in parallel but one after another, read on. If you want to get the results in order but don't care if they run in parallel, see Patrick's answer.
You could use an async
function to be able to await
promises in a for
loop, for that a promisified timer is really useful:
const timer = ms => new Promise(res => setTimeout(res, ms));
(async function() {
for(const id of ["6", "7", "7" /*...*/]) {
await timer(5000);
console.log(id);
}
console.log("done");
})();
This could also be achieved using a callback chain, however I'm not sure if that is understandable / useful (just wanted to show that callbacks doesnt have to be from hell):
["6", "7", "7" /*..*/].reduceRight(
(next, id) => () => setTimeout(() => {
console.log(id);
next();
}, 5000),
() => console.log("done")
)();
Upvotes: 4
Reputation: 51957
The solution is to promisify the callback function and then use Array.prototype.map()
coupled with Promise.all()
:
const arr = '6,7,7,8,8,5,3,5,1'
function FnWithCallback (id, cb) {
setTimeout(cb, 1000, id)
}
const promisified = id => new Promise(resolve => {
FnWithCallback(id, resolve)
})
const promises = arr.split(',').map(promisified)
Promise.all(promises).then(id => {
console.log(id)
console.log('Done')
})
If your callback API follows the Node.js convention of (error, result) => ...
, then you should use util.promisify()
to promisify the function, or check the documentation to see if omitting the callback argument will cause the call to return a promise, since a lot of packages provide promise-based APIs out-of-the-box now.
Upvotes: 5