Как выполнить обещания в серии?

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);
Другие вопросы по тегам