【问题标题】:How to create chain of asynchronous methods in node.js?如何在 node.js 中创建异步方法链?
【发布时间】:2016-12-16 05:35:35
【问题描述】:

我想在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,客户端脚本,不是节点,不是异步
  3. https://github.com/FuturesJS/FuturesJS/tree/v3 --> 已弃用,具有链化功能的较新版本 (3) 未完成。看起来像一个废弃的项目
  4. 我经常使用 async,我知道瀑布、系列和许多我经常使用的函数,我只是想将它重新排列成更简单的东西,以便在多个地方使用。我考虑如何“链接”它们,以便在其他文件中我可以使用相同的方法。

这就是为什么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)

【问题讨论】:

  • 这里需要链接什么?如果需要按顺序执行它们,请转到async library
  • 也许 async.series 就是你要找的东西 caolan.github.io/async/docs.html#series
  • Javascript 承诺也可以使用 .then() 进行链接,这是执行一系列异步任务的另一种方法。
  • 现在您不需要创建链接方法。您使用现有的一种便于链接的方法。
  • 您可以阅读有关 jQuery 如何使用队列链接动画的信息。

标签: javascript node.js


【解决方案1】:

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

所以,要做这样的事情:

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

您需要a() 来返回一个对象,该对象具有b() 方法作为属性。而且,类似地,您需要a().b() 来返回一个具有c() 作为属性的对象。这通常称为流利界面。对于同步方法,这本身就很简单。这正是 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();

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

这是队列的一般概念。当我进入这个实现时,我意识到 Promise 会让这变得容易得多。以下是不使用 Promise 的实现的总体思路(未经测试):

为简化异步操作示例,让a() 执行 10 毫秒的 setTimeout,.b() 执行 50 毫秒的 setTimeout,.c() 执行 100 毫秒的 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);

如果我们对异步操作和队列都使用 Promise,那么事情会变得简单一些:

所有异步操作,例如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
});

【讨论】:

  • 谢谢,我想既然 jQuery 动画可以做到,我为什么不能。等待你的实现
  • @DennyHiu - 我添加了第一个示例。
  • @DennyHiu - 我添加了第二个使用 Promise 的示例。
  • 我目前正在努力让您的第一个代码首先在 plunkr 中运行(作为演示,plnkr.co/edit/B8LwLWuziqnQPhcdQEok)但我不断收到错误消息:Array.prototype.call 未定义。这是完结了吗?或者这应该用于 ES2016 还是什么?我很快会在 node 中尝试一下。
  • @Dennyhiu - 该函数是您正在执行的返回承诺的任何异步操作的占位符。你提供那个。
【解决方案2】:

你可以使用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'
});

【讨论】:

  • 你的回答没有错,但我知道异步。我已经使用它了,我更新了我的问题,请看最后一部分,我解释了我为什么尝试链接异步函数的原因
  • @DennyHiue 如果你使用 ES2016,那么 async/await 可以给你你想要的,只需 await bookLibrary.debit 然后 return this,这样你就可以链接它们。
  • @PhùngĐôngHưng - ES2016 尚不包含异步/等待。你要么必须使用像 Babel 这样的东西,要么使用超越 ES2016 的前沿 JS 实现来使用 async/await 进行编程。
  • 哦,对不起,它是 ES7。有些人实现了方法链,你可以在这里结帐。 stackoverflow.com/q/32655656/4732342
【解决方案3】:

您可以通过将所有函数包含在一个对象中来做到这一点,如下所示。

var Ext = {

function a() { 
 return this;
}

function b() {
 return this;
}

}

那么你可以像下面这样称呼他们

Ext.a().b();

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

【讨论】:

  • 如果您希望能够执行 Ext.a() 并且 OP 想要链接异步操作,那么您声明对象 Ext 的语法甚至都不正确。所以,这并不能回答问题。
  • 好吧,这根本不起作用,所以它只是错误且没有用。
  • 我会寻找你对这个问题的答案,所以我也可以从中学习..
  • 我确实提供了自己的答案。
猜你喜欢
  • 1970-01-01
  • 2013-04-10
  • 1970-01-01
  • 1970-01-01
  • 2013-07-12
  • 1970-01-01
  • 2023-03-26
  • 2011-02-22
  • 2019-12-29
相关资源
最近更新 更多