Как запустить несколько асинхронных функций, а затем выполнить обратный вызов
В моем коде NodeJS мне нужно сделать 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
});
});
Я знаю, что вы могли бы сделать что-то подобное и более аккуратное с обещаниями, но я думаю, что та же концепция применима, когда следующий вызов не будет выполнен, пока текущий не завершится.
Есть ли способ вызвать все функции одновременно, но мой последний блок кода должен дождаться завершения всех вызовов API и предоставления данных перед выполнением?
4 ответа
Обещания дают вам Promise.all()
(это верно как для нативных, так и для библиотечных обещаний, таких как Bluebird).
Обновление: начиная с Node 8, вы можете использовать util.promisify()
как вы бы с Bluebird's .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
});
Похоже, async.parallel() также сделает эту работу, если вы хотите использовать async:
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);
});
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Promise.all теперь включен в ES6, поэтому вам вообще не нужны сторонние библиотеки.
"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 } ) } )
У меня был аналогичный вариант использования, когда мне приходилось делать 10 одновременных вызовов. Я сделал это с помощью комбинации
async/await
а также
Promise.all
.