Javascript 如何避免Node.js中异步函数的长嵌套

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

我想创建一个页面,显示数据库中的一些数据,因此我创建了一些函数,从我的数据库中获取这些数据。我只是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();
      });
    });
  });
如果有很多这样的函数,那么嵌套就成了一个问题


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

有趣的观察结果。注意,在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