Как я могу выполнить массив из promises в последовательном порядке?

У меня есть массив promises, который должен выполняться в последовательном порядке.

var promises = [promise1, promise2, ..., promiseN];

Вызов RSVP.all будет выполнять их параллельно:

RSVP.all(promises).then(...); 

Но как я могу запустить их в последовательности?

Я могу вручную их складывать как

RSVP.resolve()
    .then(promise1)
    .then(promise2)
    ...
    .then(promiseN)
    .then(...);

но проблема в том, что число promises меняется и массив из promises создается динамически.

Ответ 1

Если у вас уже есть их в массиве, они уже выполняются. Если у вас есть обещание, то оно уже выполняется. Это не проблема promises (I.E они не похожи на С# Task в этом отношении с помощью метода .Start()). .all ничего не выполняет он просто возвращает обещание.

Если у вас есть функция возврата обещаний:

var tasks = [fn1, fn2, fn3...];

tasks.reduce(function(cur, next) {
    return cur.then(next);
}, RSVP.resolve()).then(function() {
    //all executed
});

Или значения:

var idsToDelete = [1,2,3];

idsToDelete.reduce(function(cur, next) {
    return cur.then(function() {
        return http.post("/delete.php?id=" + next);
    });
}, RSVP.resolve()).then(function() {
    //all executed
});

Ответ 2

С помощью асинхронных функций ECMAScript 2017 это будет сделано следующим образом:

async function executeSequentially() {
    const tasks = [fn1, fn2, fn3]

    for (const fn of tasks) {
        await fn()
    }
}

Вы можете использовать BabelJS для использования асинхронных функций сейчас

Ответ 3

Вторая попытка ответа, в которой я стараюсь быть более понятным:

Во-первых, некоторый необходимый фон, из RSVP README:

Действительно удивительная часть возникает, когда вы возвращаете обещание от первого обработчика... Это позволяет сгладить вложенные обратные вызовы и является основной особенностью promises, которая предотвращает "правый дрейф" в программах с большим количеством асинхронный код.

Именно так вы делаете promises последовательным, возвращая позднее обещание от then обещания, которое должно было закончить до него.

Полезно подумать о таком наборе promises как о дереве, где ветки представляют собой последовательные процессы, а листья представляют собой параллельные процессы.

Процесс создания такого дерева promises аналогичен самой общей задаче построения других видов деревьев: поддерживать указатель или ссылку на то, где в дереве, в котором вы сейчас добавляете ветки, и итеративно добавлять вещи.

Как отметил в своем ответе @Esailija, если у вас есть массив функций, возвращающих обещание, которые не принимают аргументы, вы можете использовать reduce для аккуратной сборки дерева для вас. Если вы когда-либо реализовали сокращение для себя, вы поймете, что то, что сокращение делает за кулисами в ответе @Esailija, поддерживает ссылку на текущее обещание (cur) и каждое обещание возвращает следующее обещание в его then.

Если у вас нет хорошего массива однородных (по отношению к аргументам, которые они принимают/возвращают) обещают возвращающие функции, или если вам нужна более сложная структура, чем простая линейная последовательность, вы можете построить дерево promises самостоятельно, сохраняя ссылку на позицию в дереве обещаний, где вы хотите добавить новый promises:

var root_promise = current_promise = Ember.Deferred.create(); 
// you can also just use your first real promise as the root; the advantage of  
// using an empty one is in the case where the process of BUILDING your tree of 
// promises is also asynchronous and you need to make sure it is built first 
// before starting it

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

// etc.

root_promise.resolve();

Вы можете создавать комбинации одновременных и последовательных процессов, используя RSVP.all, чтобы добавить несколько "листьев" в "ветку" обещания. Мой пример, связанный с нисходящим для того, чтобы быть слишком сложным, показывает пример этого.

Вы также можете использовать Ember.run.scheduleOnce('afterRender'), чтобы гарантировать, что что-то сделанное с помощью одного обещания будет вынесено до того, как будет запущено следующее обещание - мой ответ слишком сложный для слишком сложного ответа также показывает пример из этого.

Ответ 4

ES7 в 2017 году.

  <script>
  var funcs = [
    _ => new Promise(res => setTimeout(_ => res("1"), 1000)),
    _ => new Promise(res => setTimeout(_ => res("2"), 1000)),
    _ => new Promise(res => setTimeout(_ => res("3"), 1000)),
    _ => new Promise(res => setTimeout(_ => res("4"), 1000)),
    _ => new Promise(res => setTimeout(_ => res("5"), 1000)),
    _ => new Promise(res => setTimeout(_ => res("6"), 1000)),
    _ => new Promise(res => setTimeout(_ => res("7"), 1000))
  ];
  async function runPromisesInSequence(promises) {
    for (let promise of promises) {
      console.log(await promise());
    }
  }
  </script>
  <button onClick="runPromisesInSequence(funcs)">Do the thing</button>

Это будет выполнять указанные функции последовательно (один за другим), а не параллельно. Параметр promises представляет собой массив функций, возвращающих Promise.

Пример Plunker с приведенным выше кодом: http://plnkr.co/edit/UP0rhD?p=preview

Ответ 5

Я собираюсь оставить этот ответ здесь, потому что это помогло бы мне, когда я придумал для решения моей проблемы.

То, что я был после, было по существу mapSeries... и мне случалось сопоставлять сохранение по набору значений... и мне нужны результаты...

Итак, здесь, насколько я понял, FWIW, чтобы помочь другим в поиске подобных вещей в будущем....

(Обратите внимание, что контекст - это приложение ember)

App = Ember.Application.create();

App.Router.map(function () {
    // put your routes here
});

App.IndexRoute = Ember.Route.extend({
    model: function () {
            var block1 = Em.Object.create({save: function() {
                return Em.RSVP.resolve("hello");
            }});
    var block2 = Em.Object.create({save: function() {
            return Em.RSVP.resolve("this");
        }});
    var block3 = Em.Object.create({save: function() {
        return Em.RSVP.resolve("is in sequence");
    }});

    var values = [block1, block2, block3];

    // want to sequentially iterate over each, use reduce, build an array of results similarly to map...

    var x = values.reduce(function(memo, current) {
        var last;
        if(memo.length < 1) {
            last = current.save();
        } else {
            last = memo[memo.length - 1];
        }
        return memo.concat(last.then(function(results) {
            return current.save();
        }));
    }, []);

    return Ember.RSVP.all(x);
    }
});

Ответ 6

У меня была аналогичная проблема, и я сделал рекурсивную функцию, которая последовательно запускает функции последовательно.

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function() {
      return executeSequentially(tasks);
    });
  }

  return Promise.resolve();  
};

Если вам нужно собрать выходные данные из этих функций:

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function(output) {
      return executeSequentially(tasks).then(function(outputs) {
        outputs.push(output);

        return Promise.resolve(outputs);  
      });
    });
  }

  return Promise.resolve([]);
};

Ответ 7

Все, что нужно решить, это цикл for:)

var promises = [a,b,c];
var chain;

for(let i in promises){
  if(chain) chain = chain.then(promises[i]);
  if(!chain) chain = promises[i]();
}

function a(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve A');
      resolve();
    },1000);
  });
}
function b(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve B');
      resolve();
    },500);
  });
}
function c(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve C');
      resolve();
    },100);
  });
}