如何在node.js中创建异步方法链?

时间:2016-12-16 05:35:35

标签: javascript node.js

我想在Node.js中创建一系列异步方法,例如:

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

每个函数都是独立的,但是当链接时,它们可以被有序地调用。例如:

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

将打印A然后C然后B。连锁店的订单可以不受限制地重新安排。 这是我搜索过的内容:

  1. Create chained methods in node.js?, - >这是关于这个话题的前一个问题,非常陈旧,也不是异步
  2. http://www.dustindiaz.com/async-method-queues/ - >取决于jquery,客户端脚本,而不是节点,而不是async
  3. https://github.com/FuturesJS/FuturesJS/tree/v3 - >不推荐使用,带有chainify功能的较新版本(3)未完成。看起来像一个废弃的项目
  4. 我经常使用 async ,我知道我经常使用的瀑布,系列和许多功能,我只想重新安排它在更多地方使用的更简单的东西。我想如何"链接"它们,以便在其他文件中我可以使用相同的方法。
  5. 我的理由async不是我期望的答案。

    考虑会计问题,如果有一笔总额为1200美元的卖出交易,首先我必须将1200美元投入资产:现金簿(借方),然后我必须将1200美元存入收入账户(信用方)。是否可以使用异步?当然是。它看起来像这样:

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

    所以我想通过链接它们来简化和更容易阅读,就像这样:

    bookLibrary.debit('cash', 1200).credit('income', 1200)
    

3 个答案:

答案 0 :(得分:3)

首先,要链接一堆函数,您需要将它们配置为公共对象上的方法。然后,每个方法都可以返回该对象,因此返回结果是对象,并且可以在该对象上调用下一个方法。

所以,做这样的事情:

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

您需要a()将具有方法b()的对象作为属性返回。而且,类似地,您需要a().b()返回一个对象c()作为属性。这通常称为Fluent界面。对于同步方法而言,这一切都非常简单。这正是jQuery如何进行链接的。

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

所有这三个调用都返回一个jQuery对象,而jQuery对象包含你可以链接的所有方法。

在上面的示例中,您可以像这样进行同步链接:

function a() {

}

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

但是,你的问题是关于异步操作。这是更多的工作,因为当你这样做:

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

这将一个接一个地立即执行所有三种方法,并且不会等待它们中的任何一种完成。使用这种确切的语法,我知道支持链接的唯一方法是构建一个队列,而不是立即实际执行.b(xxx),该对象将该操作排队,直到a()完成。这就是jQuery如下所示的动画:

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

因此,从每个方法返回的对象可以包含一个队列,当一个操作完成时,该对象然后从队列中提取下一个项目,分配它的参数(也保存在队列中),执行它和监视该异步操作,它再次从队列中提取下一个项目。

这是队列的一般概念。当我进入这个实现时,我意识到承诺会使这更容易。以下是不使用promises(未经测试)的实现的一般概念:

为了简化异步操作的示例,让make a()执行10ms setTimeout,.b()执行50ms setTimeout,.c()执行100ms setTimeout。实际上,这些可以是完成后调用回调的任何异步操作。

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

如果我们对异步操作和排队使用promises,那么事情会变得更简单:

此处的所有异步操作(例如firstAsyncOperationsecondAsyncOperation都会返回一个大大简化事物的承诺。异步链接由promise基础结构为我们完成。

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

答案 1 :(得分:1)

您可以使用async waterfall来做到这一点,这应该符合您的要求。

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

答案 2 :(得分:-1)

您可以将所有功能都包含在一个对象中,如下所示。

var Ext = {

function a() { 
 return this;
}

function b() {
 return this;
}

}

然后您可以按以下方式调用它们

Ext.a().b();

详细示例请查看我的javascript库代码,它完全符合您的需求https://github.com/waqaskhan540/MapperJs