admin管理员组文章数量:1299990
Inside this function:
function Example(array, callback){
var toReturn = [];
// variable 'array' has 2 elements in this case
array.forEach(function(el){
MongooseModel.AsyncFunction(el, function(err, doc){
if(err || !doc){ return callback('Error'); }
toReturn.push(doc)
});
});
return callback(null, toReturn)
}
A few things to note:
toReturn.push(doc)
doesn't work and on the last callback an empty array is returned. Why is that?- If there's an error, or !docs is true,
return callback('Error')
is never called.return callback(null, toReturn)
is always called.
Also, if I put a callback above the async function, like this:
function Example(array, callback){
var toReturn = [];
// variable 'array' has 2 elements in this case
array.forEach(function(el){
// another callback here
return callback('Error')
MongooseModel.AsyncFunction(el, function(err, doc){
if(err || !doc){ return callback('Error'); }
toReturn.push(doc)
});
});
return callback(null, toReturn)
}
the script crashes because multiple callbacks have been called. Why is forEach doing that and how to avoid it?
Inside this function:
function Example(array, callback){
var toReturn = [];
// variable 'array' has 2 elements in this case
array.forEach(function(el){
MongooseModel.AsyncFunction(el, function(err, doc){
if(err || !doc){ return callback('Error'); }
toReturn.push(doc)
});
});
return callback(null, toReturn)
}
A few things to note:
toReturn.push(doc)
doesn't work and on the last callback an empty array is returned. Why is that?- If there's an error, or !docs is true,
return callback('Error')
is never called.return callback(null, toReturn)
is always called.
Also, if I put a callback above the async function, like this:
function Example(array, callback){
var toReturn = [];
// variable 'array' has 2 elements in this case
array.forEach(function(el){
// another callback here
return callback('Error')
MongooseModel.AsyncFunction(el, function(err, doc){
if(err || !doc){ return callback('Error'); }
toReturn.push(doc)
});
});
return callback(null, toReturn)
}
the script crashes because multiple callbacks have been called. Why is forEach doing that and how to avoid it?
Share Improve this question asked Oct 31, 2016 at 15:12 mdakovacmdakovac 611 gold badge5 silver badges19 bronze badges 2-
"toReturn.push(doc) doesn't work and on the last callback an empty array is returned. Why is that?" It does work. But
callback(null, toReturn)
is executed beforetoReturn.push(doc)
ever had a chance to be executed. It seems like you should already know why: Because the code you execute inside theforEach
callback is asynchronous. Have a look at Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference to learn more. – Felix Kling Commented Oct 31, 2016 at 15:19 - One of these might help: stackoverflow./search?q=%5Bnode.js%5D+async+function+forEach . – Felix Kling Commented Oct 31, 2016 at 15:22
3 Answers
Reset to default 9Your 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 pleted
callback(null, toReturn);
}
});
});
}
Stuff that happens here:
Every time your
AsyncFunction
pletes, 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 plete 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/
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
.
Basically, your function executes in the following order:
- Trigger the async action for each element in
array
- Return immediately after all the async calls were triggered
- Async calls return with results, but your function returned already - so they are ending up nowhere.
In order to perform multiple async calls and return their results, you need to wait for all of them to finish. I usually solve this problem with the help of the library async, which even gives you control over how the async calls should be executed (in series or in parallel). For example, you can call async.parallel with an array of async functions:
async.parallel(array.map(function() {
return MongooseModel.AsyncFunction(...)
}), function(err, results) {
// all the async calls are finished, do something with the results
});
You should use promises because you need to wait when all async calls will be done before returning result. Here is example:
function Example(array, callback){
new Promise(function(resolve, reject) {
var toReturn = [];
array.forEach(function(el){
MongooseModel.AsyncFunction(el, function(err, doc){
if(err || !doc) {
return reject();
}
toReturn.push(doc);
if (toReturn.length === array.length) {
resolve(toReturn);
}
});
});
})
.then(callback)
.catch(function() {
callback('error');
});
}
And here you can read more about promises: https://developer.mozilla/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
本文标签: javascriptAsync function and callbacks inside a forEach loopStack Overflow
版权声明:本文标题:javascript - Async function and callbacks inside a forEach loop - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741655657a2390744.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论