Зачекайте, коли всі обіцянки завершаться, навіть якщо деякі відхиляться


405

Скажімо, у мене є набір Promises, які здійснюють мережеві запити, з яких один не зможе:

// http://does-not-exist will throw a TypeError
var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr)
  .then(res => console.log('success', res))
  .catch(err => console.log('error', err)) // This is executed   

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

Оскільки Promises.allне залишає місця для цього, який рекомендований зразок для вирішення цього питання, не використовуючи бібліотеку обіцянок?


Що слід повернути в отриманому масиві за відхилені обіцянки?
Kuba Wyrostek

9
ES6 обіцяє підтримку такого методу (і наразі, мабуть, повільніше, ніж Bluebird ). Окрім того, ще не всі браузери чи двигуни підтримують їх. Я б настійно рекомендував використовувати Bluebird, який поставляється з таким, allSettledщо задовольняє ваші потреби, без того, щоб вам було потрібно прокручувати свої власні.
Ден Комора

@KubaWyrostek Я думаю, ви пояснюєте причину Promise.all не має такої поведінки, що, на мою думку, має сенс. Це не так, як це працює, але альтернативним поглядом було б сказати Promise.all повинен повернути спеціальну обіцянку, яка ніколи не провалюється - і ви отримаєте помилку, яку викинули як аргумент, що представляє провалену обіцянку.
Натан Хаген

Щоб додати до того, що поділив Ден, всі функціональні можливості, які має Bluebird, можна використовувати за допомогою функції "відображення".
user3344977

2
@Coli: Хм, я так не думаю. Promise.allВи відкинете, як тільки будь-яка обіцянка відхилиться, тому запропонована вами фразу не гарантує, що всі обіцянки будуть виконані.
Йорг W Міттаг

Відповіді:


309

Оновлення, ймовірно, ви хочете використовувати вбудований нативний Promise.allSettled:

Promise.allSettled([promise]).then(([result]) => {
   //reach here regardless
   // {status: "fulfilled", value: 33}
});

Як цікавий факт, ця відповідь нижче була попередньою технікою в додаванні цього методу до мови:]


Звичайно, вам просто потрібно reflect:

const reflect = p => p.then(v => ({v, status: "fulfilled" }),
                            e => ({e, status: "rejected" }));

reflect(promise).then((v => {
    console.log(v.status);
});

Або з ES5:

function reflect(promise){
    return promise.then(function(v){ return {v:v, status: "fulfilled" }},
                        function(e){ return {e:e, status: "rejected" }});
}


reflect(promise).then(function(v){
    console.log(v.status);
});

Або у вашому прикладі:

var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr.map(reflect)).then(function(results){
    var success = results.filter(x => x.status === "fulfilled");
});

3
Я думаю, що це чудове рішення. Чи можете ви змінити його, щоб включити простіший синтаксис? Суть проблеми полягає в тому, що якщо ви хочете обробляти помилки в підобіцяннях, вам слід впізнати їх і повернути помилку. Так, наприклад: gist.github.com/nhagen/a1d36b39977822c224b8
Натан Хаген

3
@NathanHagen дозволяє вам з’ясувати, що відхилено, а що виконано, і витягує проблему оператору багаторазового використання.
Бенджамін Грюнбаум

4
У відповідь на власний випуск я створив такий пакет npm: github.com/Bucabug/promise-reflect npmjs.com/package/promise-reflect
SamF

2
Я зіткнувся з цим питанням деякий час тому і створив для цього пакет npm: npmjs.com/package/promise-all-soft-fail
velocity_distance

5
Це слово reflectє загальним словом в інформатиці? Чи можете ви, будь ласка, посилайтеся на те, де це пояснюється, наприклад, у вікіпедії чи щось. Я важко шукав, Promise.all not even first rejectале не знав, щоб шукати "Відбивати". Чи повинен ES6 мати такий Promise.reflect, як "Promise.all, але насправді все"?
Noitidart

253

Аналогічна відповідь, але більш ідіоматична для ES6, можливо:

const a = Promise.resolve(1);
const b = Promise.reject(new Error(2));
const c = Promise.resolve(3);

Promise.all([a, b, c].map(p => p.catch(e => e)))
  .then(results => console.log(results)) // 1,Error: 2,3
  .catch(e => console.log(e));


const console = { log: msg => div.innerHTML += msg + "<br>"};
<div id="div"></div>

В залежності від типу (ів) з значень, що повертаються, помилки часто можна виділити досить легко (наприклад , використання undefinedдля «байдуже», typeofдля звичайних значень необ'єктних, result.message, і result.toString().startsWith("Error:")т.д.)


1
@KarlBateman Я думаю, що ви розгублені. Функції порядку вирішення або відхилення тут не мають значення, оскільки .map(p => p.catch(e => e))частина перетворює всі відхилення у вирішені значення, тому Promise.allвсе одно чекає, коли все закінчиться, чи вирішать чи відхилять окремі функції, незалежно від того, скільки часу потрібно. Спробуй це.
гусек

39
.catch(e => console.log(e));ніколи не називається, тому що це ніколи не провалюється
fregante

4
@ bfred.it Це правильно. Хоча припинення ланцюгів обіцянок з, catchяк правило, є хорошою практикою IMHO .
стаксель

2
@SuhailGupta Він виявляє помилку eі повертає її як звичайне (успішне) значення. Те саме, що p.catch(function(e) { return e; })лише коротше. returnнеявна.
гусек

1
@JustinReusnow вже висвітлював у коментарях. Завжди добра практика для припинення ланцюга, якщо ви додасте код пізніше.
гусек

71

Відповідь Бенджаміна пропонує велику абстракцію для вирішення цього питання, але я сподівався на менш абстраговане рішення. Явний спосіб вирішити цю проблему - просто зателефонувати .catchна внутрішні обіцянки та повернути помилку з їх зворотного дзвінка.

let a = new Promise((res, rej) => res('Resolved!')),
    b = new Promise((res, rej) => rej('Rejected!')),
    c = a.catch(e => { console.log('"a" failed.'); return e; }),
    d = b.catch(e => { console.log('"b" failed.'); return e; });

Promise.all([c, d])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));

Promise.all([a.catch(e => e), b.catch(e => e)])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));

Зробивши цей крок далі, ви можете написати загальний обробник вилову, який виглядає приблизно так:

const catchHandler = error => ({ payload: error, resolved: false });

тоді ви можете зробити

> Promise.all([a, b].map(promise => promise.catch(catchHandler))
    .then(results => console.log(results))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!',  { payload: Promise, resolved: false } ]

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

const successHandler = result => ({ payload: result, resolved: true });

Отже, тепер ви можете зробити це:

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

Потім, щоб зберегти НУМУ, ви отримуєте відповідь Бенджаміна:

const reflect = promise => promise
  .then(successHandler)
  .catch(catchHander)

де це зараз схоже

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

Переваги другого рішення полягають у тому, що його абстрагують і сушать. Недоліком є ​​те, що у вас є більше коду, і ви повинні пам’ятати, щоб відобразити всі свої обіцянки, щоб зробити речі послідовними.

Я б охарактеризував своє рішення як явне та KISS, але насправді менш надійний. Інтерфейс не гарантує того, що ви точно знаєте, вдалося чи не виконано обіцянку.

Наприклад, у вас може бути таке:

const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));

Це не захопить a.catch, тож

> Promise.all([a, b].map(promise => promise.catch(e => e))
    .then(results => console.log(results))
< [ Error, Error ]

Немає способу сказати, хто з них був смертельним, а який - ні. Якщо це важливо, тоді ви хочете застосувати та інтерфейс, який відстежує, був він успішним чи ні (що reflectце робить).

Якщо ви просто хочете граціозно обробляти помилки, ви можете просто ставитися до помилок як невизначених значень:

> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
    .then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]

У моєму випадку мені не потрібно знати про помилку чи про те, як вона вийшла з ладу - мені просто важливо, чи є у мене значення чи ні. Я дозволю функції, яка генерує обіцянку, турбуватися про реєстрацію конкретної помилки.

const apiMethod = () => fetch()
  .catch(error => {
    console.log(error.message);
    throw error;
  });

Таким чином, решта програми може ігнорувати її помилку, якщо вона хоче, і трактувати її як невизначене значення, якщо воно хоче.

Я хочу, щоб мої функції високого рівня безпечно виходили з ладу і не турбувалися про деталі того, чому його залежності не вдалися, і я також віддаю перевагу KISS ДУХОМ, коли мені доведеться робити цей компроміс - що в кінцевому підсумку я вирішив не використовувати reflect.


1
@ Бенджамін Я думаю, що рішення Натана дуже очевидне та ідіоматичне для Promises. У той час як ваш reflectполіпшується повторне використання коду, він також встановлює інший рівень абстракції. Оскільки відповідь Натана поки що отримала лише частину надбавок порівняно з вашою, мені цікаво, чи це вказівка ​​на проблему з його рішенням, яку я ще не визнав.

2
@ LUH3417 це рішення концептуально менше звучить, оскільки трактує помилки як значення і не відокремлює помилки від не-помилок. Наприклад, якщо одна з обіцянок легітимно вирішує значення, яке можна кинути (що цілком можливо), це порушується досить погано.
Бенджамін Грюнбаум

2
@BenjaminGruenbaum Так, наприклад, new Promise((res, rej) => res(new Error('Legitimate error'))не можна було б відрізняти це new Promise(((res, rej) => rej(new Error('Illegitimate error'))? Або далі, ви не зможете фільтрувати за x.status? Я додам цю точку до своєї відповіді, щоб різниця була більш зрозумілою
Натан Хаген

3
Причина цієї поганої ідеї полягає в тому, що вона пов'язує реалізацію Обіцянки з конкретним випадком використання, який коли-небудь використовуватиметься в конкретному Promise.all()варіанті, а також стає споживачем Обіцяння знати, що конкретна обіцянка не відкине, але буде проковтнути це помилки. Насправді reflect()метод можна зробити менш «абстрактним» та більш явним, називаючи його PromiseEvery(promises).then(...). Складність відповіді, наведеної вище, порівняно з Бенджаміна повинна багато сказати про це рішення.
Ніл

33

Існує закінчена пропозиція щодо функції, яка може виконувати це на самому собі, у ванільному Javascript:, Promise.allSettledякий перейшов на етап 4, офіційний в ES2020 і реалізований у всіх сучасних умовах . Він дуже схожий на reflectфункцію в цій іншій відповіді . Ось приклад зі сторінки пропозицій. Раніше вам довелося б зробити:

function reflect(promise) {
  return promise.then(
    (v) => {
      return { status: 'fulfilled', value: v };
    },
    (error) => {
      return { status: 'rejected', reason: error };
    }
  );
}

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.all(promises.map(reflect));
const successfulPromises = results.filter(p => p.status === 'fulfilled');

Використовуючи Promise.allSettledнатомість, вищезазначене буде рівнозначним:

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');

Ті, хто використовують сучасні середовища, зможуть використовувати цей метод без бібліотек . У таких випадках наступний фрагмент повинен працювати без проблем:

Promise.allSettled([
  Promise.resolve('a'),
  Promise.reject('b')
])
  .then(console.log);

Вихід:

[
  {
    "status": "fulfilled",
    "value": "a"
  },
  {
    "status": "rejected",
    "reason": "b"
  }
]

Для більш старих браузерів, є відповідна специфікація polyfill тут .


1
Це етап 4 і повинен приземлитися в ES2020.
колба

Також доступний у вузлі 12 :)
Callum M

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

9

Мені дуже подобається відповідь Бенджаміна, і те, як він в основному перетворює всі обіцянки на завжди вирішальні, але іноді з помилкою як на результат. :)
Ось моя спроба на ваш запит на випадок, якщо ви шукали альтернативи. Цей метод просто розглядає помилки як дійсні результати і кодується аналогічно Promise.allінакше:

Promise.settle = function(promises) {
  var results = [];
  var done = promises.length;

  return new Promise(function(resolve) {
    function tryResolve(i, v) {
      results[i] = v;
      done = done - 1;
      if (done == 0)
        resolve(results);
    }

    for (var i=0; i<promises.length; i++)
      promises[i].then(tryResolve.bind(null, i), tryResolve.bind(null, i));
    if (done == 0)
      resolve(results);
  });
}

Зазвичай це називається settle. У нас це теж є у синього птаха, мені подобається відбиватися краще, але це життєздатне рішення, коли у вас є це для масиву.
Бенджамін Грюнбаум

2
Гаразд, поселення буде справді кращим ім'ям. :)
Kuba Wyrostek

Це дуже схоже на явну обіцянку будівництва антипаттера. Слід зазначити, що ви ніколи не повинні писати таку функцію самостійно, а користуватися тією, яку надає ваша бібліотека (Гаразд, рідний ES6 трохи мізерний).
Бергі

Чи можете ви використати Promiseконструктор належним чином (і уникнути цього var resolve)?
Бергі

Бергі, не соромтесь змінювати відповідь, проте вважаєш за потрібне.
Kuba Wyrostek

5
var err;
Promise.all([
    promiseOne().catch(function(error) { err = error;}),
    promiseTwo().catch(function(error) { err = error;})
]).then(function() {
    if (err) {
        throw err;
    }
});

Promise.allБуде ковтати відкинутий обіцянку і зберігати помилку в змінної, тому він буде повертатися , коли всі обіцянки були вирішені. Тоді ви можете повторно викинути помилку або зробити що завгодно. Таким чином, я гадаю, у вас вийде остання відмова замість першого.


1
Схоже, що це може агрегувати помилки, зробивши це масив і використовуючи err.push(error), тому всі помилки можуть бути збиті.
ps2goat

4

У мене була така ж проблема, і я вирішив її наступним чином:

const fetch = (url) => {
  return node-fetch(url)
    .then(result => result.json())
    .catch((e) => {
      return new Promise((resolve) => setTimeout(() => resolve(fetch(url)), timeout));
    });
};

tasks = [fetch(url1), fetch(url2) ....];

Promise.all(tasks).then(......)

У такому випадку Promise.allбудете чекати, коли кожен Обіцянок прийме resolvedабо стане rejected.

І маючи таке рішення, ми "зупиняємо catchвиконання" не блокуючим способом. Насправді ми нічого не зупиняємо, ми просто повертаємося назад Promiseу відкладеному стані, який повертає інше, Promiseколи воно вирішено після таймауту.


Але це викликає всі обіцянки за бажанням, коли ви біжите Promise.all. Я шукаю спосіб вислухати, коли всі обіцянки були використані, але не посилатись на них сам. Дякую.
SudoPlz

@SudoPlz метод all()робить це, він чекає виконання всіх Обіцянь або відхилення хоча б одного з них.
користувач1016265

це правда, але він не просто чекає, він фактично викликає / запускає / запускає процес. Якщо ви хотіли виконувати обіцянки в іншому місці, що було б неможливо, візьміть .allвсе на спалах .
SudoPlz

@SudoPlz сподіваюся, що це змінить вашу думку jsfiddle.net/d1z1vey5
user1016265

3
Я стою виправлений. До цього часу я думав, що Обіцянки виконуються лише тоді, коли хтось викликає їх (він же thenабо .allдзвінок), але вони виконуються, коли створені.
SudoPlz

2

Це має відповідати тому, як це робить Q :

if(!Promise.allSettled) {
    Promise.allSettled = function (promises) {
        return Promise.all(promises.map(p => Promise.resolve(p).then(v => ({
            state: 'fulfilled',
            value: v,
        }), r => ({
            state: 'rejected',
            reason: r,
        }))));
    };
}

2

Відповідь Бенджаміна Грюнбаума, звичайно, чудова. Але я також бачу, чи були точки зору Натана Хагена з рівнем абстракції нечіткими. Маючи короткі властивості об’єкта, якe & v теж не допомагає, але, звичайно, це можна змінити.

В JavaScript є стандартний об'єкт Error, називається Error,. В ідеалі ви завжди кидаєте примірник / нащадка цього. Перевага в тому, що ти можеш робити instanceof Error, і ти знаєш, що щось є помилкою.

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

В основному введіть помилку, якщо помилка не типу Error, загорніть помилку всередині об’єкта Error. Масив, що виходить, матиме або вирішені значення, або об'єкти Помилка, на які ви можете перевірити.

Examof всередині уловки - це на випадок, якщо ви reject("error")замість цього використовуєте якусь зовнішню бібліотеку reject(new Error("error")).

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

Ще одна перевага - це руйнування масиву просто.

const [value1, value2] = PromiseAllCatch(promises);
if (!(value1 instanceof Error)) console.log(value1);

Замість

const [{v: value1, e: error1}, {v: value2, e: error2}] = Promise.all(reflect..
if (!error1) { console.log(value1); }

Ви можете стверджувати, що !error1перевірка простіша, ніж instanceof, але вам також доведеться знищити і те й інше v & e.

function PromiseAllCatch(promises) {
  return Promise.all(promises.map(async m => {
    try {
      return await m;
    } catch(e) {
      if (e instanceof Error) return e;
      return new Error(e);
    }
  }));
}


async function test() {
  const ret = await PromiseAllCatch([
    (async () => "this is fine")(),
    (async () => {throw new Error("oops")})(),
    (async () => "this is ok")(),
    (async () => {throw "Still an error";})(),
    (async () => new Error("resolved Error"))(),
  ]);
  console.log(ret);
  console.log(ret.map(r =>
    r instanceof Error ? "error" : "ok"
    ).join(" : ")); 
}

test();


2

Замість того, щоб відкидати, вирішіть це об’єктом. Ви можете зробити щось подібне, виконуючи обіцянки

const promise = arg => {
  return new Promise((resolve, reject) => {
      setTimeout(() => {
        try{
          if(arg != 2)
            return resolve({success: true, data: arg});
          else
            throw new Error(arg)
        }catch(e){
          return resolve({success: false, error: e, data: arg})
        }
      }, 1000);
  })
}

Promise.all([1,2,3,4,5].map(e => promise(e))).then(d => console.log(d))


1
Це виглядає приємною роботою навколо, не елегантною, але буде працювати
Сонячний Тамбі

1

Я думаю, що наступне пропонує дещо інший підхід ... порівняйте fn_fast_fail()з fn_slow_fail()... хоча останній не виходить з ладу як такий ... ви можете перевірити, чи один чи обидва aта bчи є примірником, Errorі throwщо, Errorякщо ви хочете, щоб він дійшов catchблок (наприклад if (b instanceof Error) { throw b; }). Дивіться jsfiddle .

var p1 = new Promise((resolve, reject) => { 
    setTimeout(() => resolve('p1_delayed_resolvement'), 2000); 
}); 

var p2 = new Promise((resolve, reject) => {
    reject(new Error('p2_immediate_rejection'));
});

var fn_fast_fail = async function () {
    try {
        var [a, b] = await Promise.all([p1, p2]);
        console.log(a); // "p1_delayed_resolvement"
        console.log(b); // "Error: p2_immediate_rejection"
    } catch (err) {
        console.log('ERROR:', err);
    }
}

var fn_slow_fail = async function () {
    try {
        var [a, b] = await Promise.all([
            p1.catch(error => { return error }),
            p2.catch(error => { return error })
        ]);
        console.log(a); // "p1_delayed_resolvement"
        console.log(b); // "Error: p2_immediate_rejection"
    } catch (err) {
        // we don't reach here unless you throw the error from the `try` block
        console.log('ERROR:', err);
    }
}

fn_fast_fail(); // fails immediately
fn_slow_fail(); // waits for delayed promise to resolve

0

Ось мій звичай settledPromiseAll()

const settledPromiseAll = function(promisesArray) {
  var savedError;

  const saveFirstError = function(error) {
    if (!savedError) savedError = error;
  };
  const handleErrors = function(value) {
    return Promise.resolve(value).catch(saveFirstError);
  };
  const allSettled = Promise.all(promisesArray.map(handleErrors));

  return allSettled.then(function(resolvedPromises) {
    if (savedError) throw savedError;
    return resolvedPromises;
  });
};

У порівнянні з Promise.all

  • Якщо всі обіцянки будуть вирішені, воно виконує точно так, як і стандартне.

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

Для сміливих ми могли змінити Promise.all():

(function() {
  var stdAll = Promise.all;

  Promise.all = function(values, wait) {
    if(!wait)
      return stdAll.call(Promise, values);

    return settledPromiseAll(values);
  }
})();

ДЕРЖАВНО . Взагалі ми ніколи не змінюємо вбудовані модулі, оскільки це може зламати інші незв'язані бібліотеки JS або зіткнутися з майбутніми змінами стандартів JS.

Моя settledPromiseallвідстала сумісність ізPromise.all і розширює її функціональність.

Люди, які розробляють стандарти - чому б не включити це до нового стандарту Promise?


0

Promise.allз використанням сучасного async/awaitпідходу

const promise1 = //...
const promise2 = //...

const data = await Promise.all([promise1, promise2])

const dataFromPromise1 = data[0]
const dataFromPromise2 = data[1]

-1

Я б робив:

var err = [fetch('index.html').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); }),
fetch('http://does-not-exist').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); })];

Promise.all(err)
.then(function (res) { console.log('success', res) })
.catch(function (err) { console.log('error', err) }) //never executed

-1

Ви можете виконувати свою логіку послідовно за допомогою синхронного виконавця nsynjs . Він зробить паузу для кожної обіцянки, зачекає вирішення / відхилення та призначить результат вирішення dataвластивості, або викине виняток (для обробки, який вам знадобиться блок спробувати / ловити). Ось приклад:

function synchronousCode() {
    function myFetch(url) {
        try {
            return window.fetch(url).data;
        }
        catch (e) {
            return {status: 'failed:'+e};
        };
    };
    var arr=[
        myFetch("https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"),
        myFetch("https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/NONEXISTANT.js"),
        myFetch("https://ajax.NONEXISTANT123.com/ajax/libs/jquery/2.0.0/NONEXISTANT.js")
    ];
    
    console.log('array is ready:',arr[0].status,arr[1].status,arr[2].status);
};

nsynjs.run(synchronousCode,{},function(){
    console.log('done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>


-1

Я використовую наступні коди з ES5.

Promise.wait = function(promiseQueue){
    if( !Array.isArray(promiseQueue) ){
        return Promise.reject('Given parameter is not an array!');
    }

    if( promiseQueue.length === 0 ){
        return Promise.resolve([]);
    }

    return new Promise((resolve, reject) =>{
        let _pQueue=[], _rQueue=[], _readyCount=false;
        promiseQueue.forEach((_promise, idx) =>{
            // Create a status info object
            _rQueue.push({rejected:false, seq:idx, result:null});
            _pQueue.push(Promise.resolve(_promise));
        });

        _pQueue.forEach((_promise, idx)=>{
            let item = _rQueue[idx];
            _promise.then(
                (result)=>{
                    item.resolved = true;
                    item.result = result;
                },
                (error)=>{
                    item.resolved = false;
                    item.result = error;
                }
            ).then(()=>{
                _readyCount++;

                if ( _rQueue.length === _readyCount ) {
                    let result = true;
                    _rQueue.forEach((item)=>{result=result&&item.resolved;});
                    (result?resolve:reject)(_rQueue);
                }
            });
        });
    });
};

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


-1

Я знаю, що на це питання є багато відповідей, і я впевнений, що повинен (якщо не всі) правильно. Однак мені було дуже важко зрозуміти логіку / потік цих відповідей.

Тож я подивився на Оригінальну реалізацію Promise.all()і намагався наслідувати цю логіку - за винятком того, що не зупиняти виконання, якщо одна Обіця не вдалася.

  public promiseExecuteAll(promisesList: Promise<any>[] = []): Promise<{ data: any, isSuccess: boolean }[]>
  {
    let promise: Promise<{ data: any, isSuccess: boolean }[]>;

    if (promisesList.length)
    {
      const result: { data: any, isSuccess: boolean }[] = [];
      let count: number = 0;

      promise = new Promise<{ data: any, isSuccess: boolean }[]>((resolve, reject) =>
      {
        promisesList.forEach((currentPromise: Promise<any>, index: number) =>
        {
          currentPromise.then(
            (data) => // Success
            {
              result[index] = { data, isSuccess: true };
              if (promisesList.length <= ++count) { resolve(result); }
            },
            (data) => // Error
            {
              result[index] = { data, isSuccess: false };
              if (promisesList.length <= ++count) { resolve(result); }
            });
        });
      });
    }
    else
    {
      promise = Promise.resolve([]);
    }

    return promise;
  }

Пояснення:
- Переведіть курсор на вхід promisesListта виконайте кожну Обіцянку.
- Незалежно від того, чи обіцянка вирішена чи відхилена: збережіть результат Обіцянки у resultмасиві відповідно до index. Збережіть також статус вирішення / відхилення ( isSuccess).
- Після завершення всіх Обіцянок поверніть одну Обіцянку з результатом всіх інших.

Приклад використання:

const p1 = Promise.resolve("OK");
const p2 = Promise.reject(new Error(":-("));
const p3 = Promise.resolve(1000);

promiseExecuteAll([p1, p2, p3]).then((data) => {
  data.forEach(value => console.log(`${ value.isSuccess ? 'Resolve' : 'Reject' } >> ${ value.data }`));
});

/* Output: 
Resolve >> OK
Reject >> :-(
Resolve >> 1000
*/

2
Не намагайтеся повторно реалізовувати Promise.allсебе, є занадто багато речей, які можуть піти не так. Наприклад, ваша версія не обробляє порожні входи.
Берги

-4

Я не знаю, яку бібліотеку обіцянок ви використовуєте, але у більшості є щось на зразок allSettled .

Редагувати: Гаразд, оскільки ви хочете використовувати звичайний ES6 без зовнішніх бібліотек, такого методу немає.

Іншими словами: Ви повинні перевести свої обіцянки вручну та вирішити нову комбіновану обіцянку, як тільки всі обіцянки будуть виконані.


Я відредагував своє запитання, щоб уточнити - оскільки ES6 отримує обіцянки, я хотів би уникати використання іншої бібліотеки для того, що, на мою думку, є базовим функціоналом. Я думаю, що хорошим місцем для отримання відповіді було б скопіювати джерело з однієї з бібліотек обіцянок.
Натан Хаген
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.