如何运行多个异步函数然后执行回调?

Posted

技术标签:

【中文标题】如何运行多个异步函数然后执行回调?【英文标题】:How to run multiple asynchronous functions then execute callbacks? 【发布时间】:2015-12-26 00:22:04 【问题描述】:

在我的 Node.js 代码中,我需要进行 2 或 3 个 API 调用,每个调用都会返回一些数据。在所有 API 调用完成后,我想将所有数据收集到一个 JSON 对象中发送到前端。

我知道如何使用 API 回调来执行此操作(下一次调用将在上一次调用的回调中发生)但这会很慢:

//1st request
request('http://www.example.com', function (err1, res1, body) 
  
  //2nd request
  request('http://www.example2.com', function (err2, res2, body2) 
  
    //combine data and do something with it

  );

);

我知道你也可以用 Promise 做一些类似的、更简洁的事情,但我认为同样的概念适用于下一个调用在当前调用完成之前不会执行的情况。

有没有办法同时调用所有函数,但我的最后一个代码块要等待所有 API 调用完成并在执行之前提供数据?

【问题讨论】:

这是基于回调的异步函数的限制。只是没有一种超级干净的方法来做到这一点。不过,这对 Promise 来说是微不足道的。 不是重复的,而是密切相关的:How can I fetch an array of URLs with Promise.all?. 【参考方案1】:

Promise 为您提供 Promise.all()(对于原生 Promise 以及 bluebird 之类的库中的 Promise 都是如此)。

更新:从 Node 8 开始,您可以像使用 Bluebird 的 .promisify() 一样使用 util.promisify()

var requestAsync = util.promisify(request); // const util = require('util')
var urls = ['url1', 'url2'];
Promise.all(urls.map(requestAsync)).then(allData => 
    // All data available here in the order of the elements in the array
);

那么你可以做什么(原生):

function requestAsync(url) 
    return new Promise(function(resolve, reject) 
        request(url, function(err, res, body) 
            if (err)  return reject(err); 
            return resolve([res, body]);
        );
    );

Promise.all([requestAsync('url1'), requestAsync('url2')])
    .then(function(allData) 
        // All data available here in the order it was called.
    );

如果你有蓝鸟,那就更简单了:

var requestAsync = Promise.promisify(request);
var urls = ['url1', 'url2'];
Promise.all(urls.map(requestAsync)).then(allData => 
    // All data available here in the order of the elements in the array
);

【讨论】:

我想我们不能在这里避免 Promise Constructor Anti-pattern :'( @thefourtheye - 当你包装一个不返回承诺的东西时,这不是一种反模式。必须有人做出承诺。除非你有一个库函数为你做有前途的包装器,否则别无选择。 @thefourtheye 你可以很容易地做一个类似于 bluebird 的 promisify 函数来隐藏丑陋。【参考方案2】:

如果您想使用 async,听起来 async.parallel() 也可以完成这项工作:

var async = require('async');

async.parallel(
    one: function(parallelCb) 
        request('http://www.example1.com', function (err, res, body) 
            parallelCb(null, err: err, res: res, body: body);
        );
    ,
    two: function(parallelCb) 
        request('http://www.example2.com', function (err, res, body) 
            parallelCb(null, err: err, res: res, body: body);
        );
    ,
    three: function(parallelCb) 
        request('http://www.example3.com', function (err, res, body) 
            parallelCb(null, err: err, res: res, body: body);
        );
    
, function(err, results) 
    // results will have the results of all 3
    console.log(results.one);
    console.log(results.two);
    console.log(results.three);
);

【讨论】:

这里只有一个问题,我们可以不把那些并行函数一、二、三写在for循环里面吗? 我不确定你在循环中的意思。我的猜测是整个 async.parallel() 在一个循环中?我相信如果使用 async.each() 而不是使用本机循环应该没问题。 我明白了我需要什么。我在谈论 async.map(handleList, findStatus, function(err, results) ... );其中 findStatus 是一个函数,它将被调用的次数与 handleList 中存在的项目数一样多。【参考方案3】:

https://developer.mozilla.org/en-US/docs/Web/javascript/Reference/Global_Objects/Promise/all

Promise.all 现在包含在 ES6 中,因此您根本不需要任何 3rd 方库。

“Promise.all 等待所有的履行(或第一次拒绝)”

我已经设置了一个要点来演示 Promise.all() 与重构迭代:https://gist.github.com/rainabba/21bf3b741c6f9857d741b69ba8ad78b1

我正在使用 IIFE(立即调用函数表达式)。如果您不熟悉,您将希望成为下面的示例,尽管要点显示了如何使用 IIFE。 https://en.wikipedia.org/wiki/Immediately-invoked_function_expression

TL;DR

( function( promises )
    return new Promise( ( resolve, reject ) => 
        Promise.all( promises )
            .then( values => 
                console.log("resolved all promises")
                console.dir( values );
                resolve( values.reduce( (sum,value) =>  return sum+value ) ); //Use Array.prototype.reduce() to sum the values in the array
            )
            .catch( err => 
                console.dir( err );
                throw err;
            );

    );
)([ 
    new Promise( ( resolve, reject ) => 
        console.log("resolving 1");
        resolve( 1 );
    ),
    new Promise( ( resolve, reject ) => 
        console.log("resolving 2");
        resolve( 2 );
    )
 ]).then( sum =>  console.dir(  sum: sum  )  )

【讨论】:

【参考方案4】:

我有一个类似的用例,我必须进行 10 个并发调用。我是通过async/awaitPromise.all 的组合来实现的。

async function getData() 
  try 
    let result = null
    const ids = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]

    let promises = ids.map(async (id) => 
        return fetch(
          `https://jsonplaceholder.typicode.com/todos/$id`
        ).then((data) => data.json());
      );
    
    result = await Promise.all(promises)
    return result
   catch(err) 
    console.log("error: ", err)
  


getData().then(data => console.log(data))

【讨论】:

请注意,函数getData()中不需要async/await。将它们都删除,代码仍然可以正常工作。 (试试看!)原因是getData() 返回一个由.then() 调用解决的Promise。 ~ * ~ (附注:在<!-- end snippet --> 之后你根本不需要换行,但我会说这是个人喜好问题。:-)

以上是关于如何运行多个异步函数然后执行回调?的主要内容,如果未能解决你的问题,请参考以下文章

python_高级进阶同步_异步_回调函数_阻塞

协程,事件,队列,同步,异步,回调函数

进阶学习5:JavaScript异步编程——同步模式异步模式调用栈工作线程消息队列事件循环回调函数

在c ++中使用回调异步运行linux命令

flutter 同时执行多个异步请求回调

[JS] 异步事件及promise运行机制探究