Asynchronous function chaining alternatives

Asked

Viewed 2,212 times

7

I need to perform 3 select. One in each table, and its precise result use in select 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 selectField is a function where I send the query and it returns the value of select in the database.

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

  • 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?

  • Maybe yes, as I understand it, Promise allows a function to be executed only when the previous function is finished ? @Sergio

  • I’ll give you an answer ...

1 answer

12


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);
    }
}

jsFiddle: https://jsfiddle.net/c3sgt0gu/1

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
    }
);

jsFiddle: https://jsfiddle.net/9mdqqb8e/

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.

  • 1

    Great answer, I used the async.compose

Browser other questions tagged

You are not signed in. Login or sign up in order to post.