To solve this type of problem where the methods to be used are asynchronous it is necessary to analyze whether the case in question needs to chaining or parallelism.
In addition to native ways for this kind of problems there is a very useful library "Async" which I frequently use for these situations and will refer to 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 that’s hard to read and maintain:
fnA(a, function(err, resA){
fnB(b, function(err, resB){
fnC(c, function(err, resC){
fnD(d, function(err, resD){
// etc...
In some simple cases, this may be the most practical.
If you use the library async
you can use the compose
, where you can chain N functions. The rule is that each has two arguments: the variable working with the data, and the callback with erro
in the first argument and the data to pass in the second.
The example of documentation:
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 it like this:
(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);
}
}
Another way to do this is with Promises, which also allow asynchronous functions to be chained. An example would be like this, using the same example as the 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
}
);
Parallelism
Parallelism is when you have several asynchronous functions that need to be completed before you move on to the next phase of the code but do not depend on each other. Which means 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 resulted to return. It will be the case of a .map()
/ asynchronous mapping or an .forEach
/ loop / simple iterator.
If you use the library async
you can use the async.map
or async.each
if you need the results or not.
An example of documentation is like this, where you want to know the state of N files, where everyone uses 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 it like this:
(complete example here: https://jsfiddle.net/a15xupsz/)
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 the Promise.all([array de promises])
. It takes as argument an array of Promises and calls the .then
when all the languages have solved, also pass an array with the respective data of each file 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]
});
Complete example here: http://jsfiddle.net/w2vx7nj6/
Dei also an answer where you can see another practical example of Promise.all
.
Take a look at this answer http://answall.com/a/140634/129, there and the links indicate 4 different ways to do this. That’s what you’re looking for?
– Sergio
Maybe yes, as I understand it, Promise allows a function to be executed only when the previous function is finished ? @Sergio
– Jonathan
I’ll give you an answer ...
– Sergio