Javascript 如何避免Node.js中异步函数的长嵌套
我想创建一个页面,显示数据库中的一些数据,因此我创建了一些函数,从我的数据库中获取这些数据。我只是Node.js的新手,据我所知,如果我想在一个页面HTTP响应中使用所有这些,我必须将它们全部嵌套:Javascript 如何避免Node.js中异步函数的长嵌套,javascript,asynchronous,functional-programming,node.js,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>"; getSomeD
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();
});
});
});
如果有很多这样的函数,那么嵌套就成了一个问题
有没有办法避免这种情况?我想这与如何组合多个异步函数有关,这似乎是一个基本问题。有趣的观察结果。注意,在JavaScript中,通常可以用命名函数变量替换内联匿名回调函数
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); });
}
以下是:
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);
function(o,cb){
function2(o,function(err, resp){
cb(err,resp);
})
}
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();
});
});
但是,除非您计划在其他地方重用回调逻辑,否则通常更容易阅读内联匿名函数,如您的示例中所示。它还将使您不必为所有回调查找名称
此外,请注意,如下面的注释所述,如果您正在访问内部函数中的闭包变量,上述内容将不是一个简单的翻译。在这种情况下,使用内联匿名函数更可取 你需要的是一点语法糖。勾选这个:
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();
});
您所做的是采用异步模式,并将其应用于按顺序调用的3个函数,每个函数在启动前等待前一个函数完成,即,您已使它们同步。异步编程的要点是,您可以同时运行多个函数,而不必等待每个函数完成 如果getSomeDate不向getSomeOtherDate提供任何内容,而getSomeOtherDate不向getMoreData提供任何内容,那么为什么不按照js允许的方式异步调用它们,或者如果它们相互依赖且不异步,则将它们作为单个函数编写
您不需要使用嵌套来控制流—例如,通过调用一个公共函数来完成每个函数,该函数确定所有3个函数何时完成,然后发送响应。在大多数情况下,我同意Daniel Vassallo的观点。如果您可以将一个复杂且嵌套很深的函数分解为单独的命名函数,那么这通常是一个好主意。对于在单个函数中执行此操作有意义的情况,可以使用可用的多个node.js异步库中的一个。人们已经想出了很多不同的方法来解决这个问题,所以看看node.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();
});
});
这种方法的一个优点是,通过将“series”函数更改为“parallel”,可以快速更改代码以并行获取数据。更重要的是,async.js将
也可以在浏览器中工作,因此如果遇到任何棘手的异步代码,可以使用与node.js中相同的方法
希望有用 假设您可以这样做:
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();
}
]);
});
Kay,只需使用其中一个模块 它将改变这一点:
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()
}
);
我也有同样的问题。我已经看到了主要的libs-to-node运行异步函数,它们呈现出非常不自然的链接,您需要使用三个或更多的方法conf等来构建代码 我花了几个星期的时间制定了一个简单易读的解决方案。请试试看。如有任何意见,我们将不胜感激 而不是:
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();
});
});
});
要说它返回了,在我们调用的函数中:
this.return(response)
我用一种非常原始但有效的方式来做。例如,我需要得到一个有父母和孩子的模型,假设我需要为他们做单独的查询:
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]);
}
自从我找到它,我就爱上了它。它有一个可以用来避免长嵌套的功能
文件:-
seriestasks[回调]
运行一系列函数,每个函数在前一个函数完成后运行。[……]
论据
tasks—要运行的函数数组,每个函数都会传递一个回调,它必须在完成时调用。
callbackerr,[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();
});
});
使用光纤,它使异步代码看起来像是同步的,没有阻塞
我个人用这个小包装
我的项目中的代码示例每个方法实际上都是异步的,使用异步文件IO时,我甚至不敢想象使用回调或异步控制流帮助器库会有多混乱
_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"
您可以将此技巧用于数组,而不是嵌套函数或模块 眼睛容易得多
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()();
为您提供以下服务:
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);
}
我见过的最简单的语法糖 n是节点承诺 npm安装节点承诺| | git克隆 使用此选项,可以将异步方法链接为:
firstMethod().then(secondMethod).then(thirdMethod);
每个函数的返回值都可以作为下一个函数的参数使用。我最近创建了一个更简单的抽象,名为wait.for,用于在基于光纤的同步模式下调用异步函数。这是在早期阶段,但工作。网址为: 使用wait.for,您可以调用任何标准的nodejs异步函数,就像它是一个同步函数一样 使用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();
};
在其他人回答后,你说你的问题是局部变量。似乎一种简单的方法是编写一个外部函数来包含这些局部变量,然后使用一组命名的内部函数并按名称访问它们。这样,无论需要链接多少个函数,都只能嵌套两个深度 下面是我的新手尝试将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);
});
});
}
以下是使用命名内部函数进行的重写。带有_连接的外部函数也可以用作局部变量的保持器。在这里,我得到了参数sql、bindings和cb,它们的作用方式类似,但是您可以在with_connection中定义一些额外的局部变量
我一直在想,也许可以用实例变量创建一个对象,并使用这些实例变量替换局部变量。但是现在我发现上面使用嵌套函数和局部变量的方法更简单,更容易理解。似乎要花一些时间才能忘记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可以发挥一些优势。它让我摆脱了我创建的有点难看的匿名函数,这些函数除了将自己转发给方法调用之外,什么都没做。我无法直接传递该方法,因为它会包含错误的值。但是使用bind,我可以指定我想要的值
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——我只是花了几个小时。但是,也许稍微打磨一下,这项技术会有所帮助?我非常喜欢这项技术
该问题通过瀑布式命令解决:
WaterCallTasks,[回调]
运行一系列函数的数组,每个函数将其结果传递给数组中的下一个函数。但是,如果任何函数将错误传递给回调函数,则不会执行下一个函数,并且会立即使用错误调用主回调函数
论据
任务-要运行的函数数组,每个函数都传递一个callbackerr、result1、result2、。。。它必须要求完成。第一个参数是一个可以为null的错误,任何进一步的参数都将作为参数传递给下一个任务。
callbackerr,[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变量,它们将在与functionreq,res{}相同的范围内共享,functionreq,res{}包含了整个async.瀑布调用
不仅如此,async还非常干净。我的意思是我改变了很多这样的情况:
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);
function(o,cb){
function2(o,function(err, resp){
cb(err,resp);
})
}
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();
});
});
首先:
function(o,cb){
function2(o,cb);
}
然后是:
function2(o,cb);
async.waterfall([function2,function3,function4],optionalcb)
然后是:
function2(o,cb);
async.waterfall([function2,function3,function4],optionalcb)
它还允许从util.js非常快速地调用为async准备的许多预制函数。只要把你想做的事情串起来,确保o,cb被普遍处理。这大大加快了整个编码过程。为了解决这个问题,我写了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();
});
显然,还有许多其他解决方案,但预处理的优点是运行时开销很小或没有,而且由于源代码映射支持,它也很容易调试。async.js可以很好地实现这一点。我遇到了这篇非常有用的文章,它通过示例解释了async.js的需要和使用:如果您不想使用step或seq,请尝试line,这是一个简单的函数,可以减少嵌套的异步回调
类似C的AsyncWait是另一种实现方法 使用您的代码如下所示:
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);
function(o,cb){
function2(o,function(err, resp){
cb(err,resp);
})
}
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();
});
});
在纯javascript中,通过闭包可以轻松避免回调地狱。下面的解决方案假设所有回调都遵循functionerror、data signature
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();
});
P>为您所知,考虑JasZ.JS
然而,为了理解取消嵌套时的折衷,变量上的一些闭包语义可能会丢失,因此它不是直接转换。在上面的示例中,对getMoreData中的“res”的访问丢失。我认为您的解决方案已被破坏:someDataParser确实解析了所有数据,因为它还调用getMoreData。从这个意义上讲,函数名是不正确的,很明显,我们实际上没有消除嵌套问题。因此,当您有10个异步函数时,您有10个缩进级别?此链接可能有助于
P另一个问题:在getSomeDate和getSomeOtherDate之间插入另一个函数最终会改变许多行的缩进,这使得git历史更难读取。在此之后,git责怪甚至是无用的,而且手动执行此操作时可能会出现错误。execute只会一个接一个地执行部分,无需等待异步调用的结果。请注意,谢谢。我已经更新了答案。下面是一个测试:带有可选的最后一个功能Hi galambalazs,谢谢您的回答!在我的例子中,在每个缩进中都可以访问内联闭包变量。例如,函数是这样工作的:获取HTTP req/res,从DB获取用于cookie的用户ID,获取用于稍后用户ID的电子邮件,获取用于稍后电子邮件的更多数据,…,获取用于稍后Y的X,。。。如果我没有弄错的话,您建议的代码只能确保异步函数将以正确的顺序执行,但是在每个函数体中,都无法获得原始代码中闭包自然提供的变量。是这样吗?你肯定会在所有答案中失去闭包。您可以做的是在全局范围内为共享数据创建一个对象。例如,您的第一个函数添加obj.email,下一个函数使用obj.email,然后将其删除或仅分配null.Hi ngn,谢谢您的回答!在我的例子中,在每个缩进中都可以访问内联闭包变量。例如,函数是这样工作的:获取HTTP req/res,从DB获取用于cookie的用户ID,获取用于稍后用户ID的电子邮件,获取用于稍后电子邮件的更多数据,…,获取用于稍后Y的X,。。。如果我没有弄错的话,您建议的代码只能确保异步函数将以正确的顺序执行,但是在每个函数体中,都无法获得原始代码中闭包自然提供的变量。是这样吗?嗨,曹兰,谢谢你的回答!在我的例子中,在每个缩进中都可以访问内联闭包变量。例如,函数是这样工作的:获取HTTP req/res,从DB获取用于cookie的用户ID,获取用于稍后用户ID的电子邮件,获取用于稍后电子邮件的更多数据,…,获取用于稍后Y的X,。。。如果我没有弄错的话,您建议的代码只能确保异步函数将以正确的顺序执行,但是在每个函数体中,都无法获得原始代码中闭包自然提供的变量。是这样吗?您试图实现的在体系结构上称为数据管道。您可以在这种情况下使用异步瀑布。快速查看了flow js、step和async,它们似乎只处理函数执行的顺序。在我的例子中,在每个缩进中都可以访问内联闭包变量。例如,函数是这样工作的:获取HTTP req/res,从DB获取用于cookie的用户ID,获取用于稍后用户ID的电子邮件,获取用于稍后电子邮件的更多数据,…,获取用于稍后Y的X,。。。如果我没有弄错的话,这些框架只能确保异步函数以正确的顺序执行,但是在每个函数体中,没有办法获得闭包自然提供的变量。多亏了:在对这些库进行排序时,我在Github检查了每个库上的星数。async最多,大约有3000个,Step次之,大约有1000个,其他的要少得多。当然,它们并不都做相同的事情:-@KayPale我倾向于使用async.瀑布,有时每个阶段/步骤都有我自己的函数,这些函数将传递下一步需要的内容,或者在async.METHOD调用之前定义变量,以便下线可用。还将使用METHODNAME.bind。。。对于我的async.*调用,这也很有效。一个简单的问题:在您的模块列表中,最后两个是否相同?即async.js和async