To solve this type of problem where the methods to be used are asynchronous, it is necessary to analyze if the case in question needs chaining or parallelism .
In addition to native ways for this type of problem, there is a very useful library "Async" that I use frequently for these situations and I will refer to it in the examples of the answer.
Chaining
Chaining is when the functions need the result of the previous function. This is the most complex case and implies functions that wait for each other and are called sequentially.
The most obvious way , which is what you're avoiding because it generates cascading code hard to read and maintain is:
fnA(a, function(err, resA){
fnB(b, function(err, resB){
fnC(c, function(err, resC){
fnD(d, function(err, resD){
// etc...
In some simpler cases, this may be the most practical.
If you use the async
library you can use compose
, where you can chain N functions. The rule is that each has two arguments: the variable to work with the data, and the callback with erro
in the first argument and the data to pass in the second.
The documentation example:
function add1(n, callback) {
setTimeout(function () {
callback(null, n + 1);
}, 10);
}
function mul3(n, callback) {
setTimeout(function () {
callback(null, n * 3);
}, 10);
}
var add1mul3 = async.compose(mul3, add1);
add1mul3(4, function (err, result) {
// O resultado é 15
});
If you want to use native JavaScript you can do this:
(I made an example using the same Async API)
function encadear() {
var cadeia = [].slice.call(arguments);
return function(dadoInicial, end) {
var fns = cadeia.slice();
function exec(err, data) {
if (err) return end(err);
var next = fns.pop();
if (!next) return end(null, data);
next(data, exec);
}
exec(null, dadoInicial);
}
}
jsFiddle: link
Another way to do this is with Promises , that also allow you to chain asynchronous functions. An example would be like this, using the same example as above:
function waitFor(fn) {
return function(val) {
return new Promise(function(res, rej) {
fn(val, function(err, data) {
if (err) rej(err);
else res(data);
})
});
}
}
Promise.resolve(4)
.then(waitFor(add1))
.then(waitFor(mul3))
.then(function(result) {
console.log(result); // 15
}
);
jsFiddle: link
Parallelism
Parallelism is when you have several asynchronous functions that have to be completed before you move on to the next phase of the code but do not depend on each other . In other words, they can run independently and we just want to wait for the end of them all.
In this concept it is necessary to differentiate cases where you need to use the result of each of these functions or if they have not returned. It will be the case of a .map()
/ asynchronous mapping or a .forEach
/ loop / simple iterator.
If you use the library async
you can use async.map
or async.each
if you need the results or not.
An example of the documentation is thus where you want to know the state of N files, where all use a given function fs.stat
:
async.map(['file1','file2','file3'], fs.stat, function(err, results) {
// a variável "results" tem uma array na mesma ordem que os nomes dos ficheiros
// mas com os dados retornados assincronamente por "fs.stat"
});
If you want to use native JavaScript you can do this:
(full example here: link )
var stack = [
function(done) {
add1mul3(4, done);
},
function(done) {
add1mul3(3, done);
}
];
function runStack(arr, done) {
var callbacks = 0,
total = arr.length,
res = [];
for (var i = 0; i < total; i++) {
(function(index, fn) { // cria um escopo próprio
fn(function(err, val) {
if (err) console.log(err);
res[index] = val;
callbacks++;
if (callbacks == total) done(err, res);
});
})(i, arr[i]);
}
}
runStack(stack, function(err, result) {
console.log(err, result); // "null, [15, 12]"
});
Using Promises you can use Promise.all([array de promises])
. He receives as an argument an array of promises and calls .then
when all promises have resolved, also passing an array with the respective data of each promise, in the same order.
An example would be:
var fns = [5, 4, 2].map(function(nr) {
return new Promise(function(resolve, reject) {
// correr código assincrono e depois chamar
// resolve(com o valor final);
});
});
Promise.all(fns).then(function(result) {
console.log(result); // [18, 15, 9]
});
Full example here: link
I also gave an answer where you can see another practical example of Promise.all
.