Как выполнить обещания в серии?
var promiseReturningFuncs = [];
for(var i = 0; i < 5; i++){
promiseReturningFuncs.push(askQuestion);
}
var programmers = [];
Promise.reduce(promiseReturningFuncs, function(resp, x) {
console.log(typeof resp);
if(typeof resp != "function") {
programmers.push(resp);
}
return x();
})
.then(function(resp) {
programmers.push(resp);
console.log(programmers);
});
Моя цель: выполнить функцию askQuestion последовательно и разрешить массив объектов, созданных этой функцией. (эта функция должна выполняться последовательно, чтобы она могла реагировать на ввод пользователя)
Итак, представьте, что функция askQuestion возвращает обещание, которое разрешает объект, который я хочу добавить в массив.
Это мой грязный способ сделать это. Я ищу, чтобы найти более чистый способ сделать это, в идеале, мне даже не нужно было бы выдвигать массив, я бы просто получил окончательный вариант.then, где ответом является массив.
4 ответа
Поскольку вы, похоже, используете библиотеку обещаний Bluebird, у вас есть несколько встроенных опций для упорядочения функций возврата обещаний. Ты можешь использовать Promise.reduce()
, Promise.map()
со значением параллелизма 1, Promise.mapSeries
или же Promise.each()
, Если функция итератора возвращает обещание, все они будут ждать следующей итерации, пока это обещание не разрешится. То, что использовать, зависит в большей степени от механизма структурирования ваших данных и того, какой результат вы хотите (ни того, что вы на самом деле не показываете или не описываете).
Предположим, у вас есть массив функций, возвращающих обещание, и вы хотите вызывать их по одной, ожидая, пока одна из них решит, прежде чем вызывать следующую. Если вы хотите все результаты, то я бы предложил Promise.mapSeries()
:
let arrayOfPromiseReturningFunctions = [...];
// call all the promise returning functions in the array, one at a time
// wait for one to resolve before calling the next
Promise.mapSeries(arrayOfPromiseReturningFunctions, function(fn) {
return fn();
}).then(function(results) {
// results is an array of resolved results from all the promises
}).catch(function(err) {
// process error here
});
Promise.reduce()
также можно использовать, но он будет накапливать один результат, передавая его от одного к другому и заканчивая одним конечным результатом (например, Array.prototype.reduce()
делает).
Promise.map()
это более общая версия Promise.mapSeries()
это позволяет вам контролировать номер параллелизма (количество асинхронных операций в полете одновременно).
Promise.each()
также упорядочит ваши функции, но не накапливает результат. Предполагается, что у вас либо нет результата, либо вы накапливаете результат вне диапазона или через побочные эффекты. Я склонен не использовать Promise.each()
потому что я не люблю программирование побочных эффектов.
Вы можете решить эту проблему в чистом JS, используя функции ES6 (ES2015):
function processArray(arr, fn) {
return arr.reduce(
(p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
Promise.resolve([])
);
}
Он применяет функцию, заданную в массиве последовательно, и разрешает в массив результатов.
Использование:
const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));
// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);
Вы должны дважды проверить совместимость браузера, но это работает на достаточно современных Chrome (v59), NodeJS (v8.1.2) и, возможно, на большинстве других.
Вы можете использовать рекурсию, чтобы перейти к следующей итерации в then
блок.
function promiseToExecuteAllInOrder(promiseReturningFunctions /* array of functions */) {
var resolvedValues = [];
return new Promise(function(resolve, reject) {
function executeNextFunction() {
var nextFunction = promiseReturningFunctions.pop();
if(nextFunction) {
nextFunction().then(function(result) {
resolvedValues.push(result);
executeNextFunction();
});
} else {
resolve(resolvedValues);
}
}
executeNextFunction();
}
}
Выполнение одного за другим с использованием рекурсивной функции (без обещаний):
(function iterate(i,result,callback){
if( i>5 ) callback(result);askQuestion().then(res=>iterate(i+1,result.concat([res]),callback);
})(0,[],console.log);
Для уверенности это можно обернуть в обещание:
function askFive(){
return new Promise(function(callback){
(function iterate(i,result){
if( i>5 ) callback(result);askQuestion().then(res=>iterate(i+1,result.concat([res]),callback);
})(0,[],console.log);
});
}
askFive().then(console.log);
Или же:
function afteranother(i,promise){
return new Promise(function(resolve){
if(!i) return resolve([]);
afteranother(i-1,promise).then(val=>promise().then(val2=>resolve(val.concat([val2])));
});
}
afteranother(5,askQuestion).then(console.log);