Asynchronous function chaining alternatives

6

I need to make 3% with%. One in each table, and its exact result to use in the%% of% remaining.

In the original application, I do a lot of manipulations in the result of each query, which leaves a lot of content, so I summarize my problem in the following code:

selectFields('select campo1 from tabela1 where id == 1', function(value1){
    selectFields('select campo2 from tabela2 where id = 1', function(value2){
        selectFields('select campo3 from tabela3 where id = 1', function(value3){
            console.log(value3);
        });
    });
});

The select is a function where I send the query and it returns the value of the select in the database.

What I want is something more optimized than using one within another, and having to wait for the result. Imagine that I need to use more tables, it would get even bigger.

    
asked by Jonathan 15.07.2016 в 13:16

1 answer

9

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 .

    
15.07.2016 / 16:51