0

I want to create a chain of asynchronous methods in Node.js, for example:

function functionA(data, callback){
  console.log("A")
  // do something here
  callback();
}

function functionB(data, callback){
  console.log("B");
  // do something here
  callback();
}

function functionC(data, callback){
  console.log("C");
  // do something here
  callback();
}

each function is independent, but when chained, they can be called orderly. For example:

functionA(data).functionC(data).functionB(data)

will print A then C then B. The chain's order can be re-arranged without restriction. Here's what I have been searched:

  1. Create chained methods in node.js?, --> here's the previous question regarding this very topic, very old, also not async
  2. http://www.dustindiaz.com/async-method-queues/ --> depends on jquery, client side script, not node, not async
  3. https://github.com/FuturesJS/FuturesJS/tree/v3 --> deprecated, the newer version (3) with chainify feature unfinished. Looks like an abandoned project
  4. I use async a lot, I know about waterfall, series, and many functions that I use regularly, I just want to re-arrange it to something simpler to use in multiple places. I think about how to "chaining" them, so that in other file I can use the same method.

Here's my reason why async is not the answer that I expect.

Consider accounting problem, if there's a sell transaction with total $1200, first I had to put $1200 into asset:cash book(debit side), then I had to put $1200 into income book(credit side). Is it do-able with async? yes of course. it'll look like this:

async.series([
    function(callback){
        bookLibrary.debit('cash', 1200, callback);
    },
    function(callback){
        bookLibrary.credit('income', 1200, callback);
    }
]);

so I want to make it much simpler and easier to read by chaining them, like this:

bookLibrary.debit('cash', 1200).credit('income', 1200)
Community
  • 1
  • 1
DennyHiu
  • 4,861
  • 8
  • 48
  • 80
  • What is the need of chaining here? If the need is to just execute them in order, go for `async` library – RaR Dec 16 '16 at 05:37
  • 1
    Maybe `async.series` is what you are looking for http://caolan.github.io/async/docs.html#series – RaR Dec 16 '16 at 05:38
  • Javascript promises can be chained using .then() as well, which is another method of doing a series of asynchronous tasks. – PMV Dec 16 '16 at 05:39
  • Nowadays you don't create chaining methods. You use one of the existing methods which facilitate chaining. – 4castle Dec 16 '16 at 05:43
  • Thank you for comments, I want to create a simple "library" to store my accounting functions, so I can do this, for example `accounting.putDebit('book1', 500).putCredit('book2',500)`. So sometimes It can chained 2 or more functions according to my needs, it sort of like Medici (https://github.com/jraede/medici) – DennyHiu Dec 16 '16 at 05:44
  • I use async a lot, but I think it's not what I need, please see this (https://github.com/jraede/medici). I want my code can be used like that, especially in journal/record insertion example – DennyHiu Dec 16 '16 at 05:46
  • @4castle: We don't use chaining anymore ? so which is "one of the existing methods which facilitate chaining" ? would you mind to provide me an example? – DennyHiu Dec 16 '16 at 05:47
  • `Promise#then` is the primary method for chaining things. If you want to create your own library that supports chaining, it would just be returning a "subclass" of Promise that has some aliases for `then`. – 4castle Dec 16 '16 at 05:52
  • You can't really chain async methods like this unless you're going to queue up all the chained operations in some shared queue because they can't execute synchronously. This question is a bit malformed as it's not really feasible to mix synchronous chaining and async operations and it's not clear what you're really trying to do here with this make believe code. – jfriend00 Dec 16 '16 at 05:57
  • Possible duplicate of [Chaining methods with javascript](http://stackoverflow.com/questions/34898711/chaining-methods-with-javascript) – Bob_Gneu Dec 16 '16 at 06:07
  • May want to read this about the fluent interface: [What is the name of the design pattern used in method chaining in jQuery?](http://stackoverflow.com/questions/28955571/what-is-the-name-of-the-design-pattern-used-in-method-chaining-in-jquery/28955676#28955676) – jfriend00 Dec 16 '16 at 06:10
  • @Bob_Gneu, no, mine's async, the links you're provided won't work with callbacks in my case. I'm updating the title to give it more clarity – DennyHiu Dec 16 '16 at 06:31
  • @jfriend00, Thank you, but I have made very clear with first sentence, that I want to chain asynchronous function only. Do you have example, or article, anything to explain about how to "queue up all the chained operations in some shared queue"? – DennyHiu Dec 16 '16 at 06:38
  • 1
    You can read about how jQuery uses a queue to chain animations. – jfriend00 Dec 16 '16 at 06:40
  • What are you actually trying to achieve? This sounds like a really weird situation. You can surely return this from your asynchronous calls and you are off. If you want it to build the functions to execute once the previous asynchronous call is complete you are not actually talking about function chaining,though the syntax may be nice. You may want to use promises and the callbacks to hook in later callbacks to currently pending requests. What about conditionally being asynchronous? – Bob_Gneu Dec 16 '16 at 07:50
  • @Bob_Gneu, I want to make my work simpler, I often have to deal with many `async.series` in my projects and I can't make it conditionally asynchronous. So I think since jQuery animation, Transit(http://ricostacruz.com/jquery.transit/), and Medici(https://github.com/jraede/medici) can do chaining asynchronously, so why can't I?. – DennyHiu Dec 16 '16 at 08:18
  • You absolutely can and should, and it sounds like you have a couple examples you already know about so you should probably start by auditing their code base. Chaining methods is really only as complicated as returning this from your methods, what happens in between those calls is a matter of implementation. As above you can use a queue to order your calls. good luck =) – Bob_Gneu Dec 16 '16 at 17:55

3 Answers3

4

First off, to chain a bunch of functions, you need them to be configured as methods on a common object. Then, each method can return that object so the return result is the object and the next method can be called on that object.

So, to do something like this:

a().b().c();

You need a() to return an object that has the method b() on it as a property. And, similarly you need a().b() to return an object that has c() on it as a property. This is generally called the Fluent Interface. That's all pretty straightforward by itself for synchronous methods. This is exactly how jQuery does its chaining.

$(".foo").show().css("color", "blue");

All three of these calls all return a jQuery object and that jQuery object contains all the methods that you can chain.

In the example above, you could do synchronous chaining like this:

function a() {

}

a.prototype = {
    b: function() {
        // do something
        return this;
    },
    c: function() {
        // do something else
        return this;
    }
};

But, your question is about asynchronous operations. That is significantly more work because when you do:

a().b().c();

That's going to execute all three methods immediately one after the other and will not wait for any of them to complete. With this exact syntax, the only way I know of to support chaining is to build a queue where instead of actually executing .b(xxx) right away, the object queues that operation until a() finishes. This is how jQuery does animations as in:

$(".foo").slideUp().slideDown();

So, the object that is returned from each method can contain a queue and when one operation completes, the object then pulls the next item from the queue, assigns it's arguments (that are also held in the queue), executes it and monitors for that async operation to be done where it again pulls the next item from the queue.

Here's a general idea for a queue. As I got into this implementation, I realized that promises would make this a lot easier. Here's the general idea for an implementation that doesn't use promises (untested):

For simplicity of example for async operations, lets make a() execute a 10ms setTimeout, .b() a 50ms setTimeout and .c() a 100ms setTimeout. In practice, these could be any async operations that call a callback when done.

function a() {
    if (!(this instanceof a)) {
       return new a();
    } else {
        this.queue = [];
        this.inProgress = false;
        this.add(function(callback) {
            // here our sample 10ms async operation
            setTimeout(function() {
                callback(null);
            }, 10);
        }, arguments);
    }
}

a.prototype = {
    b: function() {
        this.add(function(callback) {
            // here our sample 50ms async operation
            setTimeout(function() {
                callback(null);
            }, 50);
            return this;
        }, arguments);
    },
    c: function(t) {
        this.add(function(t, callback) {
            // here our sample 100ms async operation
            setTimeout(function() {
                callback(null);
            }, t);
            return this;
        }, arguments);
    },
    add: function(fn, args) {
        // make copy of args
        var savedArgs = Array.prototype.slice.call(args);
        this.queue.push({fn: fn, args:savedArgs});
        this._next();
    },
    _next: function() {
        // execute the next item in the queue if one not already running
        var item;
        if (!this.inProgress && this.queue.length) {
            this.inProgress = true;
            item = this.queue.shift();
            // add custom callback to end of args
            item.args.push(function(err) {
                this.inProgress = false;
                if (err) {
                    // clear queue and stop execution on an error
                    this.queue = [];
                } else {
                    // otherwise go to next queued operation
                    this._next();
                }
            });
            try {
                item.fn.apply(this, item.args);
            } catch(e) {
                // stop on error
                this.queue = [];
                this.inProgress = false;
            }
        }
    }
};

// usage
a().b().c(100);

If we use promises for both our async operations and for the queuing, then things get a bit simpler:

All async operations such as firstAsyncOperation and secondAsyncOperation here return a promise which drastically simplifies things. The async chaining is done for us by the promise infrastructure.

function a(arg1, arg2) {
    if (!(this instanceof a)) {
       return new a(arg1, arg2);
    } else {
        this.p = firstAsyncOperation(arg1, arg2);
    }
}

a.prototype = {
    b: function() {
        return this._chain(secondAsyncOperation, arguments);
    },
    c: function() {
        return this._chain(thirdAsyncOperation, arguments);
    },
    _chain: function(fn, args) {
        var savedArgs = Array.prototype.slice.call(args);
        this.p = this.p.then(function() {
            return fn.apply(this, savedArgs);
        });
        return this;
    },
    then: function(a, b) {
        this.p = this.p.then(a, b);
        return this;
    },
    catch: function(fn) {
        this.p = this.p.catch(fn);
        return this;
    }
};

// usage:
a().b().c(100).then(function() {
    // done here
}).catch(function() {
    // error here
});
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thank you, I think since jQuery animation can do that why can't I. waiting for your implementation – DennyHiu Dec 16 '16 at 07:09
  • @DennyHiu - I added a first example. – jfriend00 Dec 16 '16 at 07:26
  • @DennyHiu - I added a second example using promises. – jfriend00 Dec 16 '16 at 07:36
  • I'm currently working to make your first code works in plunkr first (as a demo, https://plnkr.co/edit/B8LwLWuziqnQPhcdQEok) but I keep getting an error: Array.prototype.call is undefined. Is this finished? or Is this supposed to be used in ES2016 or something ? I'll try it in node soon. – DennyHiu Dec 16 '16 at 07:54
  • @DennyHiu - Typo in my answer. It should be `Array.prototype.slice.call`. I fixed it in the answer. FYI, I strongly recommend the second version that uses promises. Much simpler and more robust. The first should work in ES5. The second should work in ES5 plus either a promise polyfill or any implementation of ES5+ that has promises builtin. – jfriend00 Dec 16 '16 at 08:00
  • I get `firstAsyncOperation is not defined` from the second one both from node and chrome. – DennyHiu Dec 16 '16 at 08:43
  • 2
    @Dennyhiu - that function is a placeholder for whatever async operation you are doing that returns a promise. You supply that. – jfriend00 Dec 16 '16 at 21:08
  • I know this is very old, but I still use it (modified), chaining functions works like a charm, even `catch`, but I never got `then` working for some reason. Even if I use it like `.then(()=>console.log('test')` it doesn't print anything... – Sorin GFS Sep 10 '22 at 18:58
  • @SorinGFS - I think we'd need to see your actual code which would work best if you write your own question and show your actual code and describe the specific problem. – jfriend00 Sep 11 '22 at 05:56
  • Ok, I created the example, please see it [here](https://stackoverflow.com/questions/73680712/trying-to-create-chain-of-asynchronous-methods-in-node-js-then-is-not-working) – Sorin GFS Sep 11 '22 at 16:04
2

You can use async waterfall to do that, this should meet your requirement.

async.waterfall([
    function(callback) {
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback) {
        // arg1 now equals 'one' and arg2 now equals 'two'
        callback(null, 'three');
    },
    function(arg1, callback) {
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'
});
Hank Phung
  • 2,059
  • 1
  • 23
  • 38
  • 1
    your answer doesn't wrong, but I know async. I use it already, I updated my question, plase see the last part where I explain my reason why I even try to chain asynchronous functions – DennyHiu Dec 16 '16 at 07:12
  • @DennyHiue If you using ES2016 then async/await can give you what you want, just `await` `bookLibrary.debit` then `return this`, so you can chaining them. – Hank Phung Dec 16 '16 at 07:30
  • 1
    @PhùngĐôngHưng - ES2016 doesn't contain async/await yet. You either have to use something like Babel or use a leading edge JS implementation beyond ES2016 to program with async/await. – jfriend00 Dec 16 '16 at 07:40
  • Oh sorry, it's ES7. Some guys implemented method chaining yet, you can checkout here. http://stackoverflow.com/q/32655656/4732342 – Hank Phung Dec 16 '16 at 07:43
-2

You can do this by having all your functions enclosed in a single object , just like below.

var Ext = {

function a() { 
 return this;
}

function b() {
 return this;
}

}

then you can call them as below

Ext.a().b();

for detailed example please look at the code of my javascript library which does exactly what you need https://github.com/waqaskhan540/MapperJs

muhammad waqas
  • 742
  • 1
  • 5
  • 20