Як я можу виконати масив обіцянок у послідовному порядку?


81

У мене є безліч обіцянок, які потрібно виконувати в послідовному порядку.

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

Виклик RSVP.all виконуватиме їх паралельно:

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

Але, як я можу запускати їх послідовно?

Я можу вручну складати їх так

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

але проблема в тому, що кількість обіцянок змінюється, і масив обіцянок будується динамічно.


з інших відповідей і прихильників щодо мого, здається, більшій кількості людей потрібно прочитати rsvp README, де він пояснює: " Дійсна дивовижна частина настає, коли ви повертаєте обіцянку від першого обробника". Якщо ви цього не робите, ви дійсно втрачаєте виразну силу обіцянок.
Майкл Джонстон,

Схожий питання , але не рамки конкретних: stackoverflow.com/q/24586110/245966
jakub.g

Відповіді:


136

Якщо ви вже маєте їх у масиві, тоді вони вже виконуються. Якщо у вас є обіцянка, тоді вона вже виконується. Це не стосується обіцянок (тобто вони не схожі на C # 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
});

3
це чудовий спосіб побудувати дерево однорідних обіцянок, які не потребують аргументів. Це точно еквівалентно використанню вказівника next_promise для побудови дерева самостійно, що вам потрібно зробити, якщо набір обіцянок не є однорідним щодо аргументів тощо. Просто функція зменшення робить покажчик на поточний -ліст для вас. Ви також захочете побудувати дерево собі, якщо деякі ваші речі можуть відбуватися одночасно. У дереві обіцянок гілки є послідовностями, а листя - одночасними.
Майкл Джонстон,

Спасибі за вашу відповідь. Ви праві, що створення обіцянки вже означає, що вона виконується, тому моє запитання було сформовано неправильно. У підсумку я вирішив свою проблему по-іншому без обіцянок.
jaaksarv

1
@ SSHT Це добре, перш за все, ват. По-друге, попередня відповідь передається .then, у цьому прикладі вона просто ігнорується ...
Esailija

3
Якщо будь-яка з цих обіцянок провалиться, помилка ніколи не буде відхилена, і обіцянка ніколи не вирішиться ...
Максвелл

5
Якщо ви вже маєте їх у масиві, тоді вони вже виконуються. - ця фраза повинна бути напівжирним шрифтом + більшим шрифтом. Дуже важливо зрозуміти.
ducin

22

З асинхронними функціями ECMAScript 2017 це буде зроблено так:

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

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

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


Це має бути підхід за замовчуванням дотепер (2020). Для перших користувачів тут може бути важливо відзначити дві речі: 1. Коли обіцянка існує, вона вже виконується. Тому дуже важливо, щоб 2. fn1, fn2, fn3тут були функції, наприклад () => yourFunctionReturningAPromise(), на відміну від просто yourFunctionReturningAPromise(). Це також причина, чому await fn()це потрібно замість того, щоб просто await fn. Дивіться більше в офіційних документах . Вибачте за публікацію як коментар, але черга редагування заповнена :)
ezmegy

7

ES7 шлях у 2017 році.

  <script>
  var funcs = [
    _ => new Promise(resolve => setTimeout(_ => resolve("1"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("2"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("3"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("4"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("5"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("6"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("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


4

Друга спроба відповіді, в якій я намагаюся бути більш пояснювальною:

По-перше, деякі необхідні передумови з RSVP README :

Дійсно чудова частина виникає, коли ви повертаєте обіцянку від першого обробника ... Це дозволяє згладити вкладені зворотні виклики і є головною особливістю обіцянок, яка запобігає "дрейфу вправо" в програмах з великою кількістю асинхронного коду.

Це саме те, як ви даєте обіцянки послідовно, повертаючи пізнішу обіцянку з thenобіцянки, яка повинна закінчитись перед нею.

Корисно подумати про такий набір обіцянок, як дерево, де гілки представляють послідовні процеси, а листя - паралельні процеси.

Процес побудови такого дерева обіцянок є аналогічним найпоширенішому завданню побудови інших видів дерев: підтримуйте вказівник або посилання на те, де в дереві ви зараз додаєте гілки, та ітеративно додайте речі.

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

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

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'), щоб переконатися, що щось, зроблене в одній обіцянці, буде відтворено до запуску наступної обіцянки - моя прихильна до занадто складної відповіді також показує приклад цього.


3
Це набагато краще, проте я відчуваю, що ви все ще відходите від теми. Це спільне для багатьох відповідей на обіцянки, люди, схоже, не знаходять часу, щоб прочитати питання, натомість вони просто коментують якийсь аспект обіцянок, який вони особисто розуміють. Початкове запитання не передбачає паралельного виконання, навіть трохи, і воно чітко показує, що thenбажане просто ланцюгове з'єднання , ви дали багато додаткової інформації, яка приховує відповідь на запитання, яке було задано.
Девід Макмаллін,

@DavidMcMullin ".... і це чітко показує, що просто ланцюжок через це бажано ...", але насправді він заявляє, що послідовність обіцянок будується динамічно. Тому йому дійсно потрібно зрозуміти, як побудувати дерево, навіть якщо в цьому випадку це проста підмножина дерева "лінійна послідовність". Вам все одно доведеться будувати його, зберігаючи посилання на останню обіцянку в ланцюжку та додаючи до неї нові обіцянки.
Майкл Джонстон,

Коли ОП сказав, що "кількість обіцянок варіюється, а масив обіцянок будується динамічно", я майже впевнений, що він мав на увазі те, що розмір масиву не був визначений, і тому він / вона не міг використовувати просту Promise.resolve().then(...).then(...)..., не те що масив збільшувався під час виконання обіцянок. Звичайно, зараз це все спірне питання.
JLRishe

4

Ще один підхід полягає у визначенні глобальної функції послідовності на Promiseпрототипі.

Promise.prototype.sequence = async (promiseFns) => {
  for (let promiseFn of promiseFns) {
    await promiseFn();
  }
}

Тоді ви можете використовувати його де завгодно, як і Promise.all()

Приклад

const timeout = async ms => new Promise(resolve =>
  setTimeout(() => {
    console.log("done", ms);
    resolve();
  }, ms)
);

// Executed one after the other
await Promise.sequence([() => timeout(1000), () => timeout(500)]);
// done: 1000
// done: 500

// Executed in parallel
await Promise.all([timeout(1000), timeout(500)]);
// done: 500
// done: 1000

Застереження: Будьте обережні, редагуючи прототипи!


2

Все, що потрібно, щоб розв’язати 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);
  });
}

Чому if(!chain) chain = promises[i]();в ()кінці є? Я думаю, що у випадку, коли ланцюжок порожній (ітерація 0), потрібно просто отримати необроблену обіцянку, і тоді цикл може вводити кожну наступну обіцянку в ланцюжок .then(). Отже, чи не було б цього if(!chain) chain = promises[i];? Можливо, я чогось тут не зрозумів.
половина

Ах - ви a,b,cсправді функції, що повертають Promises, а не Promises. Тож вищесказане має сенс. Але яка корисність полягає в обгортанні обіцянок таким чином?
половина

2

У мене була подібна проблема, і я зробив рекурсивну функцію, яка запускає функції по черзі послідовно.

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([]);
};

0
export type PromiseFn = () => Promise<any>;

export class PromiseSequence {
  private fns: PromiseFn[] = [];

  push(fn: PromiseFn) {
    this.fns.push(fn)
  }

  async run() {
    for (const fn of this.fns) {
      await fn();
    }
  }
}

тоді

const seq = new PromiseSequence();
seq.push(() => Promise.resolve(1));
seq.push(() => Promise.resolve(2));
seq.run();

можна також зберегти те, що обіцянки повертаються, в інший приватний var і передати його зворотним викликам


-1

Справа, за якою я переслідував, була по суті mapSeries, і я випадково мапую збереження за набором значень, і я хочу результати.

Отже, ось наскільки я зрозумів, щоб допомогти іншим шукати подібні речі в майбутньому ..

(Зверніть увагу, що контекст - це програма 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);
    }
});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.