问题
I'm using Node.js and the async library, however I keep seeing the error: Callback was already called
.
I think I understand why I get the error, however I don't know if it is actually possible to do the following/how can resolve.
Basically I want both the innercallbacks to have completed before the outercallback is completed.
So the code with which I am facing this issue looks like:
async.forEachLimit(inData, 25, function (data, innercallback) {
myJson.matches.forEach(function (oMatches) {
if (data.$.id == oMatches.SourceId) {
oMatches.ids.forEach(function (odId) {
client.execute("g.addV('test').property('id', \"" + data.$.id + "\")", {},
function (err) {
setTimeout(function () { innercallback(err) }, 2000);
});
client.execute("g.V('" + data.$.id + "').addE('matches').to(g.V('xyz'))", {},
function (err) {
setTimeout(function () { innercallback(err) }, 2000);
});
})
} //there is no else case.
});
}, outercallback);
Btw - I'm using setTimeout
along with async.forEachLimit
to reduce number of requests made to Azure (as I don't have too many)
回答1:
Promise can be used to ensure the order of asynchronized callbacks.
Take a look at async#AsyncFunction in the official document
of async
package
Wherever we accept a Node-style async function, we also directly accept an ES2017 async function. In this case, the async function will not be passed a final callback argument, and any thrown error will be used as the err argument of the implicit callback, and the return value will be used as the result value. (i.e. a rejected of the returned Promise becomes the err callback argument, and a resolved value becomes the result.)
The third argument of the async#forEachLimit is an async#AsyncFunction
so you can just return a promise from it and later resolve the promise to indicate that its job has finished.
Your code can be improved as follows,
async.forEachLimit(inData, 25, function (data, innercallback) {
// we will wait for all async function in here to complete
// then call the innercallback
var asyncPromises = []
myJson.matches.forEach(function (oMatches) {
if (data.$.id == oMatches.SourceId) {
oMatches.ids.forEach(function (odId) {
asyncPromises.push(new Promise(function (resolve, reject) {
client.execute("g.addV('test').property('id', \"" + data.$.id + "\")", {},
function (err) {
setTimeout(function () {
if (err) {
reject(err)
return
}
resolve();
}, 2000);
});
}))
asyncPromises.push(new Promise(function (resolve, reject) {
client.execute("g.V('" + data.$.id + "').addE('matches').to(g.V('xyz'))", {},
function (err) {
setTimeout(function () {
if (err) {
reject(err)
return
}
resolve();
}, 2000);
});
}))
})
} //there is no else case.
})
// Here we can ensure that innercallback is called only for once
Promise.all(asyncPromises)
.then(function () {
innercallback(null)
})
.catch(function (err) {
// handle the error here
innercallback(err)
})
}, outercallback);
Note that you should make sure you have Promise
support in your Node environment. Builtin Promise
is supported after Node v6. Check out here.
Updated
I've misunderstood you internal callbacks which both of them must have completed before outercallback is called. I've corrected it with Promise.all
, which takes an array of Promise
as arguments and return a Promise
which will be resolved if all sub-Promise
s are all resolved or rejected if one of sub-Promise
s is rejected. See Promise#all.
Updated (2018.05.14)
You have to ensure that every innercallback
received from AsyncFunction (i.e. the 3rd argument of async#forEachLimit
) of the async
package is called only once during each iteration. Especially to be careful when you do Array#forEach
in each iteration since it may make you call innercallback
multiple times in an iteration.
I've update the code block above, where I take away all calls to innercallback
from the callback of client.execute
and put all client.execute
into a promise. After that I collect all promises into the asyncPromises
array.
Promise#all
is used to ensure that all promises are resolved (i.e. all client.execute
s are finished) then finally call innercallback
. Or if one of the promise above is rejected and is caught in Promise#catch
, call innercallback
with the first argument to be the error reason.
回答2:
You are calling innercallback
twice in case both client.execute
throw error. You can use async.parallel function or Promise.all
, here is example of async.parallel
and also you need to call innercallback
function in else
block
async.forEachLimit(inData, 25, function(data, innercallback) {
async.eachSeries(myJson.matches, function(oMatches, callback) {
if (data.$.id == oMatches.SourceId) {
async.eachSeries(oMatches.ids, function(odId, callback) {
async.parallel([
function(callback) {
client.execute("g.addV('test').property('id', \"" + data.$.id + "\")", {}, callback);
},
function(callback) {
client.execute("g.V('" + data.$.id + "').addE('matches').to(g.V('xyz'))", {}, callback);
}
], callback);
}, callback)
} else {
callback();
}
}, innercallback);
}, outercallback);
Update: updated code, now using async.eachSeries
in the place for Array.forEach
来源:https://stackoverflow.com/questions/50265790/node-js-async-multiple-innercallbacks-within-a-if-statement