Callback after all asynchronous forEach callbacks are completed
Array.forEach
does not provide this nicety (oh if it would) but there are several ways to accomplish what you want:
Using a simple counter
function callback () { console.log('all done'); }
var itemsProcessed = 0;
[1, 2, 3].forEach((item, index, array) => {
asyncFunction(item, () => {
itemsProcessed++;
if(itemsProcessed === array.length) {
callback();
}
});
});
(thanks to @vanuan and others) This approach guarantees that all items are processed before invoking the "done" callback. You need to use a counter that gets updated in the callback. Depending on the value of the index parameter does not provide the same guarantee, because the order of return of the asynchronous operations is not guaranteed.
Using ES6 Promises
(a promise library can be used for older browsers):
Process all requests guaranteeing synchronous execution (e.g. 1 then 2 then 3)
function asyncFunction (item, cb) {
setTimeout(() => {
console.log('done with', item);
cb();
}, 100);
}
let requests = [1, 2, 3].reduce((promiseChain, item) => {
return promiseChain.then(() => new Promise((resolve) => {
asyncFunction(item, resolve);
}));
}, Promise.resolve());
requests.then(() => console.log('done'))Process all async requests without "synchronous" execution (2 may finish faster than 1)
let requests = [1,2,3].map((item) => {
return new Promise((resolve) => {
asyncFunction(item, resolve);
});
})
Promise.all(requests).then(() => console.log('done'));
Using an async library
There are other asynchronous libraries, async being the most popular, that provide mechanisms to express what you want.
EditThe body of the question has been edited to remove the previously synchronous example code, so i've updated my answer to clarify.
The original example used synchronous like code to model asynchronous behaviour, so the following applied:
array.forEach
is synchronous and so is res.write
, so you can simply put your callback after your call to foreach:
posts.foreach(function(v, i) {
res.write(v + ". index " + i);
});
res.end();
Best way to wait for .forEach() to complete
If there is no asynchronous code inside the forEach
, forEach
is not asynchronous, for example in this code:
array.forEach(function(item){
//iterate on something
});
alert("Foreach DONE !");
you will see the alert after forEach
finished.
Otherwise (You have something asynchronous inside), you can wrap the forEach
loop in a Promise:
var bar = new Promise((resolve, reject) => {
foo.forEach((value, index, array) => {
console.log(value);
if (index === array.length -1) resolve();
});
});
bar.then(() => {
console.log('All done!');
});
Credit: @rolando-benjamin-vaz-ferreira
Executing a callback function after .forEach finishes
This is a case where the more modern approach is to use promises
function asyncFunction(item) { // return a promise return new Promise((resolve, reject) => { setTimeout(() => { console.log("in async function, item is " + item) // resolve promise when data is ready resolve(item) }, Math.random()*2000)// random delay to stagger order of returns
})
}
// create array of promiseslet promiseArray = [1, 2, 3].map(asyncFunction);
// runs when all promises are resolvedPromise.all(promiseArray).then(results => { console.log('all done') // results array will be in same order as original array console.log('results are: ', results)})
.as-console-wrapper {max-height: 100%!important;top:0}
Javascript - Callback after all nested forEach loops are completed
Using forEach
's 2nd parameter, the index, you can carry out a check whether all loops are done each time you run the innermost loop.
Thus with only a few lines added to your code you get this:
const scanFiles = function(accounts, cb) {
let dirs = ['pending', 'done', 'failed'];
let jobs = [];
accounts.forEach(function(account, accIndex) {
dirs.forEach(function(dir, dirIndex) {
fs.readdir(account + '/' + dir, function(err, files) {
files.forEach(function(file, fileIndex) {
//do something
//add file to jobs array
jobs.push(file);
// Check whether each loop is on its last iteration
const filesDone = fileIndex >= files.length - 1;
const dirsDone = dirIndex >= dirs.length - 1;
const accsDone = accIndex >= accounts.length - 1;
// all three need to be true before we can run the callback
if (filesDone && dirsDone && accsDone) {
cb(jobs);
}
});
});
});
});
}
run a callback function after forEach loop finish
Another approach using async-await
to avoid callback hell.
const usersProfile = async () => {
let interval = 1000;
const delayPromise = (data, delayDuration) => {
return new Promise((resolve) => {
setTimeout(() => {
if(data.status === 'online'){
console.log(`${data.name} ${data.items} ${data.profile} ${data.images}`);
resolve();
}
}, delayDuration)
});
};
try {
const userData = await db.select('*').from('users').returning('*');
const promises = userData.map((data, index) => delayPromise(data, index * interval));
await Promise.all(promises);
// here you can write your effects
} catch (e) {
console.log(e);
}
}
How to wait for forEach to complete with asynchronous callbacks?
Iterable.forEach
, Map.forEach
, and Stream.forEach
are meant to execute some code on each element of a collection for side effects. They take callbacks that have a void
return type. Consequently, those .forEach
methods cannot use any values returned by the callbacks, including returned Future
s. If you supply a function that returns a Future
, that Future
will be lost, and you will not be able to be notified when it completes. You therefore cannot wait for each iteration to complete, nor can you wait for all iterations to complete.
Do NOT use .forEach
with asynchronous callbacks.
Instead, if you want to wait for each asynchronous callback sequentially, just use a normal for
loop:
for (var mapEntry in gg.entries) {
await Future.delayed(const Duration(seconds: 5));
}
(In general, I recommend using normal for
loops over .forEach
in all but special circumstances. Effective Dart has a mostly similar recommendation.)
If you really prefer using .forEach
syntax and want to wait for each Future
in succession, you could use Future.forEach
(which does expect callbacks that return Future
s):
await Future.forEach(
gg.entries,
(entry) => Future.delayed(const Duration(seconds: 5)),
);
If you want to allow your asynchronous callbacks to run concurrently (and possibly in parallel), you can use Future.wait
:
await Future.wait([
for (var mapEntry in gg.entries)
Future.delayed(const Duration(seconds: 5)),
]);
See https://github.com/dart-lang/linter/issues/891 for a request for an analyzer warning if attempting to use an asynchronous function as a Map.forEach
or Iterable.forEach
callback (and for a list of many similar StackOverflow questions).
JavaScript - Run callback after all fetch requests to finish in multiple for loops - errored or not
Push all of your promises into an array, and then use Promise.allSettled(promises).then((results) => {})
Documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
Example:
const promises = images.map(async (image) => {
// do some async work
return fetch(whatever); // don't .catch this
})
Promise.allSettled(promises).then((results) => {
// results is a result of errors or successes
})
Async function and callbacks inside a forEach loop
Your questions
toReturn.push(doc) doesn't work and on the last callback an empty array is returned. Why is that?
You're calling callback
before MongooseModel.AsyncFunction
gets any chance to execute your function(err,doc)
.
If there's an error, or !docs is true, return callback('Error') is never called. return callback(null, toReturn) is always called.
The successful return is called immediately after scheduling your async functions. It's still possible that the callback('Error')
will be called sometime later by MongooseModel.AsyncFunction
.
the script crashes because multiple callbacks have been called
That's exactly the behaviour you asked for! Your second code literally says: "For each element, call the callback once".
Some pointers
All in all, I think the single thing you have wrong is that calling the callback somewhat resembles returning from the function.
That's not the case! Your function Example
is supposed to schedule some async things to happen, then return immediately (returning nothing) but promising that either callback(error)
or callback(null, success)
will be called sometime later in the future.
Therefore, it doesn't make sense to ever say return callback()
- just callback()
will do. By the time that you have some data to call callback
with, the function Example
will already have finished executing. The function that eventually calls callback
will the an anonymous function passed to MongooseModel.AsyncFunction
, not Example
itself.
Now what?
You could try an approach like this:
function Example(array, callback){
var toReturn = [];
var previousError = false;
array.forEach(function(el){
MongooseModel.AsyncFunction(el, function(err, doc){
if (previousError) {
return;
}
if(err || !doc) {
previousError = true;
callback('Error');
}
toReturn.push(doc)
if (toReturn.length === array.length) {
// that was the last push - we have completed
callback(null, toReturn);
}
});
});
}
Stuff that happens here:
Every time your
AsyncFunction
completes, you aggregate the result. If that was the last one, you can finally callcallback
and pass the whole data. (Otherwise, we're waiting for a few more functions, so nocallback
)If there was an error somewhere along the way, we report it immediately, but also take a note that the error has been reported already, so that further executions of
AsyncFunction
that have been scheduled don't do anything in particular. This prevents yourcallback
from potentially being called twice or more.Be careful though - the order of elements inside
toReturn
will be random, depending on which async tasks complete first.
But this is messy
Oh yes. That's why we don't really do callbacks anymore. There's a pattern called promises
that makes it significantly easier to work with async callback spaghetti, I suggest to read up a little on this topic before moving forward.
- https://www.promisejs.org/
Let's make it neat
Using promises, this kind of code would look something like:
function Example(array) {
var promises = array.map(function(el) {
return MongooseModel.AsyncFunction(el);
});
return Promise.all(promises);
}
which is much shorter and also doesn't have any problems with error handling or ordering of output items like our previous example.
Usage is simple - instead of this
Example([1, 2, 3], function(error, results) {
if (error !== null) {
// ...
} else {
// ...
}
})
you call like this:
Example([1, 2, 3]).then(
function(results) {
// ...
},
function(error) {
// ...
}
);
Oh, this relies on MongooseModel.AsyncFunction
returning a promise on itself, but Mongoose already does this and so do most libraries. If a library doesn't, you can easily add the promise support (see NodeRedis for example).
How to do something with each result of AsyncFunction
before returning it?
Easy too!
function Example(array) {
var promises = array.map(function(el) {
return MongooseModel.AsyncFunction(el).then(function(doc) {
return doc + "some modifications";
});
});
return Promise.all(promises);
}
Or if you need additional error handling:
function Example(array) {
var promises = array.map(function(el) {
return MongooseModel.AsyncFunction(el).then(function(doc) {
if (theresSomethingWrongWith(doc) {
return Promise.reject(new Error("ouch!"));
}
return doc + "some modifications";
});
});
return Promise.all(promises);
}
Think of returning a rejected promise as something similar to raising an exception - it will naturally bubble up all the way to the returning promise of Example
.
Run callback function after forEach is done
What you want to do is to make all your asynchronous processes converge into a single promise that you can use to execute the callback at the correct moment.
Lets start at the point each process is complete, which I assume is in the callback passed to the mv()
function in unzipData()
. You want to wrap each of these asynchronous actions in a Promise that resolves in the callback and you also want to use these promises later and for that you use the .map()
method to collect the promises in an array (instead of .forEach()
).
Here's the code:
var promises = filelist.map((filePath) => {
if (!filePath.endsWith('.zip')) {
var components = filePath.split('/');
var component = components[components.length-1];
return new Promise((resolve, reject) =>
mv(filePath, datadir + `processing/${uid}/${component}`, (err) => {
if(err) {
console.log('move file err: ');
reject(); // Or resolve() if you want to ignore the error and not cause it to prevent the callback from executing later
} else {
console.log('move file done');
resolve();
}
}));
}
return Promise.resolve();
});
(if the asynchronous action is not to be executed, a Promise that resolves immediately is returned instead)
Now, we can turn this list of Promises into a single Promise that resolves when all of the promises in the list has resolved:
var allPromise = Promise.all(promises);
Next, we need to look further up in the code. We can see that the code we've just been looking at is itself part of an event handler of an asynchronous action, i.e. fs.createReadStream()
. You need to wrap that in a promise that gets resolved when the inner promises resolve and this is the promise that the unzipData()
function shall return:
function unzipData(path, fileName, uid) {
console.log('get into unzip');
console.log('creating: ', path + fileName);
return new Promise((outerResolve) =>
fs.createReadStream(path + fileName)
.pipe(unzip.Extract({path: path}))
.on('close', () => {
console.log('get into unzip close');
var filelist = listFile(path);
// Code from previous examples
allPromise.then(outerResolve);
}));
}
Next, we look at the functions that use unzipData()
: getDataReg()
and getDataFtp()
. They only perform one asynchronous action so all you need to do is to make them return a promise that resolves when the promise returned by unzipData()
resolves.
Simplified example:
exports.getDataReg = (url, uid) => {
return new Promise((resolve, reject) => {
// ...
https.get(url, (response) => {
response.pipe(file);
file.on('finish', () => {
unzipData(datadir + `download/${uid}/`, fileName, uid)
.then(resolve);
});
}).on('error', (err) => { // Handle errors
fs.unlink(datadir + `download/${uid}/${fileName}`);
reject(); // Or resolve() if you want to ignore the error and not cause it to prevent the callback from executing later
});
// ...
});
}
Finally, we get to the processArray()
function and here you need to do the same thing we did to begin with: map the processes into a list of promises. First, the process
function passed needs to return the promises returned by getDataReg()
and getDataFtp()
:
// Second param, download and post process
(url) => {
if(url.startsWith('http')) {
return getDataReg(url, uid);
}
else if(url.startsWith('ftp')) {
return getDataFtp(url, uid);
}
else {
console.log('not a valid resource');
}
return Promise.reject(); // or Promise.resolve() if you want invalid resources to be ignored and not prevent the callback from executing later
}
Now, your processArray()
function can look like this:
exports.processArray = (items, process, callback) =>
Promise.all(items.map(process))
.then(callback)
.catch(() => console.log('Something went wrong somewhere'));
Your callback will get invoked when all asynchronous actions have completed, regardless of in which order they do. If any one of the promises rejects, the callback will never be executed so manage your promise rejections accordingly.
Here's a JSFiddle with the complete code: https://jsfiddle.net/upn4yqsw/
Related Topics
How to Fire and Forget a Promise in Nodejs (Es7)
Webdriver Click() VS JavaScript Click()
Call ASP.NET Function from JavaScript
How to Add New Array Elements at the Beginning of an Array in JavaScript
Browsers, Time Zones, Chrome 67 Error (Historic Timezone Changes)
Constructors in JavaScript Objects
Async/Await Always Returns Promise
How to Set Focus on an Input Field After Rendering
Sorting a JavaScript Object by Property Name
Trying to Fire the Onload Event on Script Tag
What's Wrong with Awaiting a Promise Chain
How to Access Svg Elements with JavaScript
How to Send a Variable Number of Arguments to a JavaScript Function
Jquery's Jquery-1.10.2.Min.Map Is Triggering a 404 (Not Found)
How to Pass Data from a Page to Another Page Using React Router
What Does (Function($) {})(Jquery); Mean