如何避免在Node.js中长时间嵌套异步函数

时间:2010-11-20 19:43:45

标签: javascript asynchronous functional-programming node.js

我想创建一个显示来自数据库的一些数据的页面,所以我创建了一些从我的数据库中获取数据的函数。我只是Node.js的新手,所以据我所知,如果我想在一个页面中使用所有这些(HTTP响应),我必须将它们全部嵌套:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

如果有很多这样的功能,那么嵌套就成了问题

有没有办法避免这种情况?我想这与你如何组合多个异步函数有关,这似乎是一个基本的东西。

23 个答案:

答案 0 :(得分:73)

有趣的观察。请注意,在JavaScript中,您通常可以使用命名函数变量替换内联匿名回调函数。

以下内容:

http.createServer(function (req, res) {
   // inline callback function ...

   getSomeData(client, function (someData) {
      // another inline callback function ...

      getMoreData(client, function(moreData) {
         // one more inline callback function ...
      });
   });

   // etc ...
});

可以重写看起来像这样:

var moreDataParser = function (moreData) {
   // date parsing logic
};

var someDataParser = function (someData) {
   // some data parsing logic

   getMoreData(client, moreDataParser);
};

var createServerCallback = function (req, res) {
   // create server logic

   getSomeData(client, someDataParser);

   // etc ...
};

http.createServer(createServerCallback);

但是,除非您计划在其他地方重用回调逻辑,否则通常更容易读取内联匿名函数,如您的示例所示。它还可以使您不必为所有回调找到一个名称。

另外请注意,在下面的注释中注明@pst,如果您正在访问内部函数中的闭包变量,则上述内容不是简单的翻译。在这种情况下,使用内联匿名函数更为可取。

答案 1 :(得分:63)

凯,只需使用其中一个模块。

它会转变:

dbGet('userIdOf:bobvance', function(userId) {
    dbSet('user:' + userId + ':email', 'bobvance@potato.egg', function() {
        dbSet('user:' + userId + ':firstName', 'Bob', function() {
            dbSet('user:' + userId + ':lastName', 'Vance', function() {
                okWeAreDone();
            });
        });
    });
});

进入这个:

flow.exec(
    function() {
        dbGet('userIdOf:bobvance', this);

    },function(userId) {
        dbSet('user:' + userId + ':email', 'bobvance@potato.egg', this.MULTI());
        dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI());
        dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI());

    },function() {
        okWeAreDone()
    }
);

答案 2 :(得分:18)

在大多数情况下,我同意Daniel Vassallo的观点。如果你可以将一个复杂且深度嵌套的函数分解为单独的命名函数,那么这通常是一个好主意。对于在单个函数中执行它的时间,您可以使用许多可用的node.js异步库之一。人们已经提出了许多不同的方法来解决这个问题,所以请看一下node.js模块页面,看看你的想法。

我自己为此编写了一个模块,名为async.js。使用此,上面的示例可以更新为:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  async.series({
    someData: async.apply(getSomeDate, client),
    someOtherData: async.apply(getSomeOtherDate, client),
    moreData: async.apply(getMoreData, client)
  },
  function (err, results) {
    var html = "<h1>Demo page</h1>";
    html += "<p>" + results.someData + "</p>";
    html += "<p>" + results.someOtherData + "</p>";
    html += "<p>" + results.moreData + "</p>";
    res.write(html);
    res.end();
  });
});

这种方法的一个好处是,您可以通过将“系列”功能更改为“并行”来快速更改代码以并行获取数据。更重要的是,async.js会 也可以在浏览器中工作,因此如果您遇到任何棘手的异步代码,您可以使用与node.js相同的方法。

希望这很有用!

答案 3 :(得分:18)

您可以将此技巧用于数组而不是嵌套函数或模块。

眼睛容易得多。

var fs = require("fs");
var chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step3");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step4");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step5");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("done");
    },
];
chain.shift()();

您可以扩展并行进程甚至并行进程链的惯用语:

var fs = require("fs");
var fork1 = 2, fork2 = 2, chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        var next = chain.shift();
        fs.stat("f2a.js",next);
        fs.stat("f2b.js",next);
    },
    function(err, stats) {
        if ( --fork1 )
            return;
        console.log("step3");
        var next = chain.shift();

        var chain1 = [
            function() { 
                console.log("step4aa");
                fs.stat("f1.js",chain1.shift());
            },
            function(err, stats) { 
                console.log("step4ab");
                fs.stat("f1ab.js",next);
            },
        ];
        chain1.shift()();

        var chain2 = [
            function() { 
                console.log("step4ba");
                fs.stat("f1.js",chain2.shift());
            },
            function(err, stats) { 
                console.log("step4bb");
                fs.stat("f1ab.js",next);
            },
        ];
        chain2.shift()();
    },
    function(err, stats) {
        if ( --fork2 )
            return;
        console.log("done");
    },
];
chain.shift()();

答案 4 :(得分:15)

为此,我非常喜欢async.js

问题由瀑布命令解决:

  

瀑布(任务,[回调])

     

运行一系列函数,每个函数将结果传递给数组中的下一个。但是,如果任何函数将错误传递给回调,则不执行下一个函数,并立即调用主回调并显示错误。

     

参数

     

任务 - 要运行的函数数组,每个函数都传递一个回调函数(错误,结果1,结果2,...),它必须在完成时调用。第一个参数是一个错误(可以为null),任何进一步的参数将作为参数传递给下一个任务。   callback(err,[results]) - 一旦完成所有函数,就会运行一个可选的回调函数。这将传递最后一个任务的回调结果。

实施例

async.waterfall([
    function(callback){
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        callback(null, 'three');
    },
    function(arg1, callback){
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'    
});

对于req,res变量,它们将在与函数(req,res){}相同的范围内共享,它包含整个async.waterfall调用。

不仅如此,异步非常干净。我的意思是我改变了很多像这样的案例:

function(o,cb){
    function2(o,function(err, resp){
        cb(err,resp);
    })
}

首先:

function(o,cb){
    function2(o,cb);
}

然后到此:

function2(o,cb);

然后到此:

async.waterfall([function2,function3,function4],optionalcb)

它还允许从util.js快速调用为async准备的许多预制函数。只需将您想要做的事情链接起来,确保o,cb得到普遍处理。这大大加快了整个编码过程。

答案 5 :(得分:11)

你需要的是一些语法糖。谢谢这个:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = ["<h1>Demo page</h1>"];
  var pushHTML = html.push.bind(html);

  Queue.push( getSomeData.partial(client, pushHTML) );
  Queue.push( getSomeOtherData.partial(client, pushHTML) );
  Queue.push( getMoreData.partial(client, pushHTML) );
  Queue.push( function() {
    res.write(html.join(''));
    res.end();
  });
  Queue.execute();
}); 

非常 整洁 ,不是吗?你可能会注意到html变成了一个数组。这部分是因为字符串是不可变的,所以最好在数组中缓冲输出,而不是丢弃更大和更大的字符串。另一个原因是因为bind的另一个很好的语法。

示例中的

Queue实际上只是一个示例,与partial一起可以实现如下

// Functional programming for the rescue
Function.prototype.partial = function() {
  var fun = this,
      preArgs = Array.prototype.slice.call(arguments);
  return function() {
    fun.apply(null, preArgs.concat.apply(preArgs, arguments));
  };
};

Queue = [];
Queue.execute = function () {
  if (Queue.length) {
    Queue.shift()(Queue.execute);
  }
};

答案 6 :(得分:7)

自从我找到它之后,我就爱上了Async.js。它具有async.series功能,可用于避免长嵌套。

文档: -


系列(任务,[回调])

运行一系列函数,每个函数在前一个函数完成后运行。 [...]

参数

tasks - 要运行的函数数组,每个函数都传递一个必须在完成时调用的回调。 callback(err, [results]) - 完成所有功能后运行的可选回调。此函数获取传递给数组中使用的回调的所有参数的数组。


以下是我们如何将其应用于您的示例代码: -

http.createServer(function (req, res) {

    res.writeHead(200, {'Content-Type': 'text/html'});

    var html = "<h1>Demo page</h1>";

    async.series([
        function (callback) {
            getSomeData(client, function (someData) { 
                html += "<p>"+ someData +"</p>";

                callback();
            });
        },

        function (callback) {
            getSomeOtherData(client, function (someOtherData) { 
                html += "<p>"+ someOtherData +"</p>";

                callback(); 
            });
        },

        funciton (callback) {
            getMoreData(client, function (moreData) {
                html += "<p>"+ moreData +"</p>";

                callback();
            });
        }
    ], function () {
        res.write(html);
        res.end();
    });
});

答案 7 :(得分:6)

我见过的最简单的语法糖就是节点承诺。

npm install node-promise || git clone https://github.com/kriszyp/node-promise

使用此方法可以将异步方法链接为:

firstMethod().then(secondMethod).then(thirdMethod);

每个的返回值在下一个中作为参数提供。

答案 8 :(得分:3)

你所做的是采用异步模式并将其应用于按顺序调用的3个函数,每个函数在开始之前等待前一个函数完成 - 即你已经使它们同步。关于异步编程的一点是,您可以同时运行多个函数,而不必等待每个函数完成。

如果getSomeDate()没有为getSomeOtherDate()提供任何东西,它没有为getMoreData()提供任何内容,那么为什么不像js允许的那样异步调用它们,或者它们是相互依赖的(而不是异步的)写入它们作为一个单一的功能?

您不需要使用嵌套来控制流程 - 例如,通过调用确定所有3个已完成的公共函数来完成每个函数的完成,然后发送响应。

答案 9 :(得分:2)

在封闭的纯JavaScript中可以很容易地避免回调地狱。下面的解决方案假设所有回调都遵循函数(错误,数据)签名。

http.createServer(function (req, res) {
  var modeNext, onNext;

  // closure variable to keep track of next-callback-state
  modeNext = 0;

  // next-callback-handler
  onNext = function (error, data) {
    if (error) {
      modeNext = Infinity;
    } else {
      modeNext += 1;
    }
    switch (modeNext) {

    case 0:
      res.writeHead(200, {'Content-Type': 'text/html'});
      var html = "<h1>Demo page</h1>";
      getSomeDate(client, onNext);
      break;

    // handle someData
    case 1:
        html += "<p>"+ data +"</p>";
        getSomeOtherDate(client, onNext);
        break;

    // handle someOtherData
    case 2:
      html += "<p>"+ data +"</p>";
      getMoreData(client, onNext);
      break;

    // handle moreData
    case 3:
      html += "<p>"+ data +"</p>";
      res.write(html);
      res.end();
      break;

    // general catch-all error-handler
    default:
      res.statusCode = 500;
      res.end(error.message + '\n' + error.stack);
    }
  };
  onNext();
});

答案 10 :(得分:2)

假设您可以这样做:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    var html = "<h1>Demo page</h1>";
    chain([
        function (next) {
            getSomeDate(client, next);
        },
        function (next, someData) {
            html += "<p>"+ someData +"</p>";
            getSomeOtherDate(client, next);
        },
        function (next, someOtherData) {
            html += "<p>"+ someOtherData +"</p>";
            getMoreData(client, next);
        },
        function (next, moreData) {
            html += "<p>"+ moreData +"</p>";
            res.write(html);
            res.end();
        }
    ]);
});

你只需要实现chain(),以便它将每个函数部分地应用到下一个函数,并立即只调用第一个函数:

function chain(fs) {
    var f = function () {};
    for (var i = fs.length - 1; i >= 0; i--) {
        f = fs[i].partial(f);
    }
    f();
}

答案 11 :(得分:1)

我最近创建了一个名为 wait.for 的简单抽象来在同步模式下调用异步函数(基于Fibers)。它处于早期阶段,但有效。它在:

https://github.com/luciotato/waitfor

使用 wait.for ,您可以调用任何标准nodejs async函数,就像它是同步函数一样。

使用 wait.for 您的代码可以是:

var http=require('http');
var wait=require('wait.for');

http.createServer(function(req, res) {
  wait.launchFiber(handleRequest,req, res); //run in a Fiber, keep node spinning
}).listen(8080);


//in a fiber
function handleRequest(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  var someData = wait.for(getSomeDate,client);
  html += "<p>"+ someData +"</p>";
  var someOtherData = wait.for(getSomeOtherDate,client);
  html += "<p>"+ someOtherData +"</p>";
  var moreData = wait.for(getMoreData,client);
  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
};

...或者如果你想减少冗长(并且还添加错误捕获)

//in a fiber
function handleRequest(req, res) {
  try {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(
    "<h1>Demo page</h1>" 
    + "<p>"+ wait.for(getSomeDate,client) +"</p>"
    + "<p>"+ wait.for(getSomeOtherDate,client) +"</p>"
    + "<p>"+ wait.for(getMoreData,client) +"</p>"
    );
    res.end();
  }
  catch(err) {
   res.end('error '+e.message); 
  }

};

在所有情况下, getSomeDate getSomeOtherDate getMoreData 应该是标准的异步函数,最后一个参数是函数回调(错误,数据)

如:

function getMoreData(client, callback){
  db.execute('select moredata from thedata where client_id=?',[client.id],
       ,function(err,data){
          if (err) callback(err);
          callback (null,data);
        });
}

答案 12 :(得分:1)

为了解决这个问题,我编写了nodent(https://npmjs.org/package/nodent),它无形地预处理你的JS。您的示例代码将变为(异步,真正 - 阅读文档)。

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  someData <<= getSomeDate(client) ;

  html += "<p>"+ someData +"</p>";
  someOtherData <<= getSomeOtherDate(client) ;

  html += "<p>"+ someOtherData +"</p>";
  moreData <<= getMoreData(client) ;

  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
});

显然,还有许多其他解决方案,但预处理具有很少或没有运行时开销的优势,并且由于源映射支持,它也很容易调试。

答案 13 :(得分:0)

因为你知道Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase


    const jj = require('jazz.js');

    // ultra-compat stack
    jj.script([
        a => ProcessTaskOneCallbackAtEnd(a),
        b => ProcessTaskTwoCallbackAtEnd(b),
        c => ProcessTaskThreeCallbackAtEnd(c),
        d => ProcessTaskFourCallbackAtEnd(d),
        e => ProcessTaskFiveCallbackAtEnd(e),
    ]);

答案 14 :(得分:0)

使用wire您的代码如下所示:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});

    var l = new Wire();

    getSomeDate(client, l.branch('someData'));
    getSomeOtherDate(client, l.branch('someOtherData'));
    getMoreData(client, l.branch('moreData'));

    l.success(function(r) {
        res.write("<h1>Demo page</h1>"+
            "<p>"+ r['someData'] +"</p>"+
            "<p>"+ r['someOtherData'] +"</p>"+
            "<p>"+ r['moreData'] +"</p>");
        res.end();
    });
});

答案 15 :(得分:0)

C#-like asyncawait是另一种方法

https://github.com/yortus/asyncawait

async(function(){

    var foo = await(bar());
    var foo2 = await(bar2());
    var foo3 = await(bar2());

}

答案 16 :(得分:0)

如果您不想使用&#34; step&#34;或&#34; seq&#34;,请尝试&#34; line&#34;这是一个减少嵌套异步回调的简单函数。

https://github.com/kevin0571/node-line

答案 17 :(得分:0)

async.js适用于此。我遇到了这篇非常有用的文章,它通过示例解释了async.js的需求和使用:http://www.sebastianseilund.com/nodejs-async-in-practice

答案 18 :(得分:0)

其他人回复之后,你说你的问题是局部变量。这似乎是一种简单的方法是编写一个外部函数来包含那些局部变量,然后使用一堆命名的内部函数并按名称访问它们。这样,无论你需要连接多少函数,你都只能嵌套两个深层。

以下是我的新手尝试使用mysql Node.js模块进行嵌套:

function with_connection(sql, bindings, cb) {
    pool.getConnection(function(err, conn) {
        if (err) {
            console.log("Error in with_connection (getConnection): " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, function(err, results) {
            if (err) {
                console.log("Error in with_connection (query): " + JSON.stringify(err));
                cb(true);
                return;
            }
            console.log("with_connection results: " + JSON.stringify(results));
            cb(false, results);
        });
    });
}

以下是使用命名内部函数的重写。外部函数with_connection也可以用作局部变量的持有者。 (在这里,我得到了以类似方式行事的参数sqlbindingscb,但您可以在with_connection中定义一些额外的局部变量。)

function with_connection(sql, bindings, cb) {

    function getConnectionCb(err, conn) {
        if (err) {
            console.log("Error in with_connection/getConnectionCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, queryCb);
    }

    function queryCb(err, results) {
        if (err) {
            console.log("Error in with_connection/queryCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        cb(false, results);
    }

    pool.getConnection(getConnectionCb);
}

我一直在想,或许可以使用实例变量创建一个对象,并使用这些实例变量来替换局部变量。但现在我发现使用嵌套函数和局部变量的上述方法更简单,更容易理解。需要一些时间来忘掉OO,似乎: - )

所以这是我以前的版本,包含对象和实例变量。

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, function(err, results) { self.query(err, results); });
}
DbConnection.prototype.query = function(err, results) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        self.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    self.cb(false, results);
}

function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    pool.getConnection(function (err, conn) { dbc.getConnection(err, conn); });
}

事实证明,bind可以用来获得某些优势。它允许我摆脱我创建的有点丑陋的匿名函数,除了将自己转发给方法调用之外没有做任何事情。我无法直接传递该方法,因为它会涉及this的错误值。但是使用bind,我可以指定我想要的this值。

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var f = this.query.bind(this);
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, f);
}
DbConnection.prototype.query = function(err, results) {
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    this.cb(false, results);
}

// Get a connection from the pool, execute `sql` in it
// with the given `bindings`.  Invoke `cb(true)` on error,
// invoke `cb(false, results)` on success.  Here,
// `results` is an array of results from the query.
function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    var f = dbc.getConnection.bind(dbc);
    pool.getConnection(f);
}

当然,这些都不是使用Node.js编码的正确JS - 我只花了几个小时就完成了。但也许稍微抛光这种技术可以帮助吗?

答案 19 :(得分:0)

Task.js 为您提供:

spawn(function*() {
    try {
        var [foo, bar] = yield join(read("foo.json"),
                                    read("bar.json")).timeout(1000);
        render(foo);
        render(bar);
    } catch (e) {
        console.log("read failed: " + e);
    }
});

而不是:

var foo, bar;
var tid = setTimeout(function() { failure(new Error("timed out")) }, 1000);

var xhr1 = makeXHR("foo.json",
                   function(txt) { foo = txt; success() },
                   function(err) { failure() });
var xhr2 = makeXHR("bar.json",
                   function(txt) { bar = txt; success() },
                   function(e) { failure(e) });

function success() {
    if (typeof foo === "string" && typeof bar === "string") {
        cancelTimeout(tid);
        xhr1 = xhr2 = null;
        render(foo);
        render(bar);
    }
}

function failure(e) {
    xhr1 && xhr1.abort();
    xhr1 = null;
    xhr2 && xhr2.abort();
    xhr2 = null;
    console.log("read failed: " + e);
}

答案 20 :(得分:0)

使用Fibers https://github.com/laverdet/node-fibers它使异步代码看起来像同步(没有阻塞)

我个人使用这个小包装http://alexeypetrushin.github.com/synchronize 来自我的项目的代码示例(每个方法实际上是异步的,使用异步文件IO)我甚至害怕想象回调或async-control-flow帮助库会有什么混乱。

_update: (version, changesBasePath, changes, oldSite) ->
  @log 'updating...'
  @_updateIndex version, changes
  @_updateFiles version, changesBasePath, changes
  @_updateFilesIndexes version, changes
  configChanged = @_updateConfig version, changes
  @_updateModules version, changes, oldSite, configChanged
  @_saveIndex version
  @log "updated to #{version} version"

答案 21 :(得分:0)

我以非常原始但有效的方式做到这一点。例如。我需要与其父母和孩子一起获得一个模型,让我们说我需要为他们做单独的查询:

var getWithParents = function(id, next) {
  var getChildren = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      },
      getParents = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      }
      getModel = function(id, next) {
        /*... code ... */
        if (model) {
          // return next callbacl
          return next.pop()(model, next);
        } else {
          // return last callback
          return next.shift()(null, next);
        }
      }

  return getModel(id, [getParents, getChildren, next]);
}

答案 22 :(得分:0)

我遇到了同样的问题。我已经看到节点运行异步函数的主要库,它们呈现非自然链接(你需要使用三个或更多方法confs等)来构建你的代码。

我花了几周时间开发一个简单易用的解决方案。请试试EnqJS。所有意见将不胜感激。

而不是:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

使用EnqJS:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";

  enq(function(){
    var self=this;
    getSomeDate(client, function(someData){
      html += "<p>"+ someData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getSomeOtherDate(client, function(someOtherData){ 
      html += "<p>"+ someOtherData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getMoreData(client, function(moreData) {
      html += "<p>"+ moreData +"</p>";
      self.return();
      res.write(html);
      res.end();
    });
  });
});

观察到代码看起来比以前更大。但它并没有像以前那样嵌套。 为了显得更自然,链被立即称为:

enq(fn1)(fn2)(fn3)(fn4)(fn4)(...)

并且说它返回了,我们称之为函数:

this.return(response)