处理node.js回调金字塔


9

我刚刚开始使用节点,并且我很快注意到的一件事是回调能够以多快的速度建立到一个愚蠢的缩进级别:

doStuff(arg1, arg2, function(err, result) {
    doMoreStuff(arg3, arg4, function(err, result) {
        doEvenMoreStuff(arg5, arg6, function(err, result) {
            omgHowDidIGetHere();
        });
    });
});

负责人风格指南说,把每个回调在一个单独的功能,但似乎对使用封闭的过于严格,并使单个对象在顶级声明可用几层下来,对象必须通过所有的传递中间回调。

可以使用函数范围来帮助吗?将所有需要访问全局对象的回调函数放在声明该对象的函数中,以便将其放入闭包中?

function topLevelFunction(globalishObject, callback) {

    function doMoreStuffImpl(err, result) {
        doMoreStuff(arg5, arg6, function(err, result) {
            callback(null, globalishObject);
        });
    }

    doStuff(arg1, arg2, doMoreStuffImpl);
}

等等更多的层...

还是有框架等可以帮助减少缩进级别而无需为每个回调都声明命名函数?您如何处理回调金字塔?


2
请注意闭包的其他问题-在JS闭包中,它会捕获整个父上下文(在其他语言中,它仅捕获所使用的变量或用户专门请求的变量),如果回调层次结构足够深,例如,回调保留在某处。
尤金

Answers:


7

承诺在异步行为和接口之间提供了明确的关注点分离,因此可以在没有回调的情况下调用异步函数,并且可以在通用的Promise接口上完成回调交互。

“承诺”有多种实现:


例如,您可以重写此嵌套的回调

http.get(url.parse("http://test.com/"), function(res) {
    console.log(res.statusCode);
    http.get(url.parse(res.headers["location"]), function(res) {
        console.log(res.statusCode);
    });
});

喜欢

httpGet(url.parse("http://test.com/")).then(function (res) {
    console.log(res.statusCode);
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);
});

而不是回调中的回调,a(b(c()))您可以链接“ .then” a().then(b()).then(c())


这里的介绍:http : //howtonode.org/promises


您介意对这些资源的用途进行更多解释吗?为什么建议您在回答所提问题时推荐这些资源?在Stack Exchange上不太欢迎“仅链接的答案”
gnat

1
好的,对不起。我添加了一个示例和更多信息。
法比恩·萨

3

作为Promise 的替代方法,您应该yield结合EcmaScript 6中引入的生成器函数来查看关键字。这两者现在都可以在Node.js 0.11.x版本中使用,但需要--harmony在运行Node时另外指定标志.js:

$ node --harmony app.js

使用这些构造和库,例如TJ Holowaychuk的co,可以使您以看起来像同步代码的样式编写异步代码,尽管它仍以异步方式运行。基本上,这些东西共同实现了对Node.js的协同例程支持。

基本上,您需要为运行异步程序的代码编写一个生成器函数,在其中调用异步程序,但在其前面加上yield关键字。因此,最终您的代码如下所示:

var run = function * () {
  var data = yield doSomethingAsync();
  console.log(data);
};

要运行此生成器函数,您需要一个库,例如前面提到的co。呼叫如下所示:

co(run);

或者,将其内联:

co(function * () {
  // ...
});

请注意,在生成器函数中,您可以调用其他生成器函数,所有您需要做的就是yield再次给它们加上前缀。

有关此主题的介绍,请在Google中搜索诸如之类的术语,yield generators es6 async nodejs然后您将找到大量信息。要花一段时间才能习惯它,但是一旦掌握了它,就永远不会回头。

请注意,这不仅为您提供了更好的语法来调用函数,而且还使您可以使用常规的(同步)控制流逻辑,例如for循环或try/ catch。不再有很多回调和所有这些事情。

祝好运并玩得开心点 :-)!


0

现在,您有了asyncawait软件包,其语法与Node 中&将来的本机支持应该非常相似awaitasync

基本上,它允许您编写看起来像同步的异步代码,从而显着降低了LOC和缩进级别,但要以牺牲一点性能为代价(与原始回调相比,程序包所有者的速度为79%)进行权衡,希望在本机支持可用时减少。

当性能不是主要考虑因素,并且同步书写样式更适合您的项目需求时,仍然是摆脱IMO的噩梦之谜的好选择。

包doc中的基本示例:

var foo = async (function() {
    var resultA = await (firstAsyncCall());
    var resultB = await (secondAsyncCallUsing(resultA));
    var resultC = await (thirdAsyncCallUsing(resultB));
    return doSomethingWith(resultC);
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.