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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4234619/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 11:31:19  来源:igfitidea点击:

How to avoid long nesting of asynchronous functions in Node.js

javascriptasynchronousfunctional-programmingnode.js

提问by Kay Pale

I want to make a page that displays some data from a DB, so I have created some functions that get that data from my DB. I'm just a newbie in Node.js, so as far as I understand, if I want to use all of them in a single page (HTTP response) I'd have to nest them all:

我想制作一个页面来显示来自数据库的一些数据,所以我创建了一些从我的数据库获取数据的函数。我只是 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();
      });
    });
  });

If there are many functions like that, then the nesting becomes a problem.

如果有很多这样的函数,那么嵌套就会成为一个问题

Is there a way to avoid this? I guess it has to do with how you combine multiple asynchronous functions, which seems to be something fundamental.

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

采纳答案by Daniel Vassallo

Interesting observation. Note that in JavaScript you can normally replace inline anonymous callback functions with named function variables.

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

The following:

下列:

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 ...
});

Could be rewritten to look something like this:

可以重写为如下所示:

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);

However unless you plan to reuse to callback logic in other places, it is often much easier to read inline anonymous functions, as in your example. It will also spare you from having to find a name for all the callbacks.

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

In addition note that as @pstnoted in a comment below, if you are accessing closure variables within the inner functions, the above would not be a straightforward translation. In such cases, using inline anonymous functions is even more preferable.

另外请注意,正如@pst在下面的评论中指出的那样,如果您在内部函数中访问闭包变量,则上述内容将不是直接的翻译。在这种情况下,使用内联匿名函数更可取。

回答by Baggz

Kay, simply use one of these modules.

凯,只需使用这些模块之一。

It will turn this:

它会变成这样:

dbGet('userIdOf:bobvance', function(userId) {
    dbSet('user:' + userId + ':email', '[email protected]', function() {
        dbSet('user:' + userId + ':firstName', 'Bob', function() {
            dbSet('user:' + userId + ':lastName', 'Vance', function() {
                okWeAreDone();
            });
        });
    });
});

Into this:

进入这个:

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

    },function(userId) {
        dbSet('user:' + userId + ':email', '[email protected]', this.MULTI());
        dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI());
        dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI());

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

回答by Caolan

For the most part, I'd agree with Daniel Vassallo. If you can break up a complicated and deeply nested function into separate named functions, then that is usually a good idea. For the times when it makes sense to do it inside a single function, you can use one of the many node.js async libraries available. People have come up with lots of different ways to tackle this, so take a look at the node.js modules page and see what you think.

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

I've written a module for this myself, called async.js. Using this, the above example could be updated to:

我自己为此编写了一个模块,称为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();
  });
});

One nice thing about this approach is that you can quickly change your code to fetch the data in parallel by changing the 'series' function to 'parallel'. What's more, async.js will also work inside the browser, so you can use the same methods as you would in node.js should you encounter any tricky async code.

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

Hope that's useful!

希望有用!

回答by Guido

You could use this trick with an array rather than nested functions or a module.

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

Far easier on the eyes.

在眼睛上容易得多。

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()();

You can extend the idiom for parallel processes or even parallel chains of processes:

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

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()();

回答by Grant Li

I like async.jsa lot for this purpose.

为此,我非常喜欢async.js

The issue is solved by waterfall command:

该问题通过瀑布命令解决:

waterfall(tasks, [callback])

Runs an array of functions in series, each passing their results to the next in the array. However, if any of the functions pass an error to the callback, the next function is not executed and the main callback is immediately called with the error.

Arguments

tasks - An array of functions to run, each function is passed a callback(err, result1, result2, ...) it must call on completion. The first argument is an error (which can be null) and any further arguments will be passed as arguments in order to the next task. callback(err, [results]) - An optional callback to run once all the functions have completed. This will be passed the results of the last task's callback.

瀑布(任务,[回调])

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

参数

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

Example

例子

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'    
});

As for the req,res variables, they will be shared within the same scope as function(req,res){} which enclosed the whole async.waterfall call.

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

Not only so, async is very clean. What I means is that I change a lot of cases like this:

不仅如此,async 非常干净。我的意思是我改变了很多这样的情况:

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

To first:

首先:

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

Then to this:

然后到这个:

function2(o,cb);

Then to this:

然后到这个:

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

It also allows many premade functions prepared for async to be called from util.js very fast. Just chain up what you want to do, make sure o,cb is universally handled. This speeds up the whole coding process a lot.

它还允许从 util.js 非常快速地调用许多为异步准备的预制函数。只要把你想做的事情串联起来,确保 o,cb 被普遍处理。这大大加快了整个编码过程。

回答by gblazex

What you need is a bit of syntactic sugar. Chek this out:

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

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();
}); 

Pretty neat, isn't it? You may notice that html became an array. That's partly because strings are immutable, so you better off with buffering your output in an array, than discarding larger and larger strings. The other reason is because of another nice syntax with bind.

整洁,不是吗?您可能会注意到 html 变成了一个数组。这部分是因为字符串是不可变的,所以最好将输出缓冲在数组中,而不是丢弃越来越大的字符串。另一个原因是因为bind.

Queuein the example is really just an example and along with partialcan be implemented as follows

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);
  }
};

回答by Salman von Abbas

Am in love Async.jsever since I found it. It has a async.seriesfunction you can use to avoid long nesting.

自从我发现Async.js就爱上了它。它有一个async.series可以用来避免长嵌套的功能。

Documentation:-

文件:-



series(tasks, [callback])

系列(任务,[回调])

Run an array of functions in series, each one running once the previous function has completed. [...]

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

Arguments

参数

tasks- An array of functions to run, each function is passed a callback it must call on completion. callback(err, [results])- An optional callback to run once all the functions have completed. This function gets an array of all the arguments passed to the callbacks used in the array.

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



Here's how we can apply it to your example code:-

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

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();
    });
});

回答by Nikhil Ranjan

The most simple syntactical sugar i have seen is node-promise.

我见过的最简单的语法糖是 node-promise。

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

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

Using this you can chain async methods as:

使用它,您可以将异步方法链接为:

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

The return value of each is available as argument in the next.

每个的返回值都可用作下一个的参数。

回答by Nick Tulett

What you have done there is take an asynch pattern and apply it to 3 functions called in sequence, each one waiting for the previous one to complete before starting - i.e. you have made them synchronous. The point about asynch programming is that you can have several functions all running at once and not have to wait for each to complete.

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

if getSomeDate() doesn't provide anything to getSomeOtherDate(), which doesn't provide anything to getMoreData() then why don't you call them asynchronously as js allows or if they are interdependent (and not asynchronous) write them as a single function?

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

You don't need to use nesting to control the flow - for instance, get each function to finish by calling a common function that determines when all 3 have completed and then sends the response.

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

回答by ngn

Suppose you could do this:

假设你可以这样做:

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();
        }
    ]);
});

You only need to implement chain() so that it partially applies each function to the next one, and immediately invokes only the first function:

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

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