Помилки обробки в Promise.all


266

У мене є масив Обіцянь, які я вирішую Promise.all(arrayOfPromises);

Я продовжую продовжувати ланцюжок обіцянок. Виглядає приблизно так

existingPromiseChain = existingPromiseChain.then(function() {
  var arrayOfPromises = state.routes.map(function(route){
    return route.handler.promiseHandler();
  });
  return Promise.all(arrayOfPromises)
});

existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
  // do stuff with my array of resolved promises, eventually ending with a res.send();
});

Я хочу додати заяву про вилов для обробки окремої обіцянки у випадку помилки, але коли я намагаюся, Promise.allповертає першу виявлену помилку (ігнорує решту), і тоді я не можу отримати дані з решти обіцянок у масив (це не помилка).

Я намагався робити щось на кшталт ..

existingPromiseChain = existingPromiseChain.then(function() {
      var arrayOfPromises = state.routes.map(function(route){
        return route.handler.promiseHandler()
          .then(function(data) {
             return data;
          })
          .catch(function(err) {
             return err
          });
      });
      return Promise.all(arrayOfPromises)
    });

existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
      // do stuff with my array of resolved promises, eventually ending with a res.send();
});

Але це не вирішує.

Дякую!

-

Редагувати:

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

Ланцюг сервера Node Express

serverSidePromiseChain
    .then(function(AppRouter) {
        var arrayOfPromises = state.routes.map(function(route) {
            return route.async();
        });
        Promise.all(arrayOfPromises)
            .catch(function(err) {
                // log that I have an error, return the entire array;
                console.log('A promise failed to resolve', err);
                return arrayOfPromises;
            })
            .then(function(arrayOfPromises) {
                // full array of resolved promises;
            })
    };

API-виклик (route.async call)

return async()
    .then(function(result) {
        // dispatch a success
        return result;
    })
    .catch(function(err) {
        // dispatch a failure and throw error
        throw err;
    });

Якщо зібрати .catchдля Promise.allдо того , як .thenздається, має на меті зловити будь-які помилки від первинних обіцянок, але потім повертаючи весь масив до наступного.then

Дякую!


2
Ваша спроба здається, що вона повинна спрацювати… можливо, є десь інша проблема пізніше?
Ри-

.then(function(data) { return data; })можна повністю пропустити
Бергі

Єдина причина, що вищезгадане не повинно вирішуватись, це те, що там ви не показуєте нам весь код у thenабо catchобробниках, і все-таки помилка закидається всередину. До речі, це вузол?

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

Відповіді:


189

Promise.allє все або нічого. Він вирішує , як всі обіцянки в рішучістю масиву, або відкинути , як тільки один з них відхиляє. Іншими словами, він або вирішує масив усіх розв’язаних значень, або відхиляє з однією помилкою.

У деяких бібліотеках є щось Promise.when, що називається , і я, наскільки я розумію, замість того, щоб чекати, коли всі обіцянки в масиві будуть вирішені або відхилені, але я не знайомий з цим, і це не в ES6.

Ваш код

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

Єдина причина, з якої я можу подумати, чому це не "вирішиться", - це те, що він не працює в коді, ви не показуєте нам, і причина, по якій ви не бачите жодного повідомлення про помилку, полягає в тому, що цей ланцюжок обіцянок не припиняється остаточним ловити (наскільки ви все одно нам показуєте).

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

Promise.all(state.routes.map(function(route) {
  return route.handler.promiseHandler().catch(function(err) {
    return err;
  });
}))
.then(function(arrayOfValuesOrErrors) {
  // handling of my array containing values and/or errors. 
})
.catch(function(err) {
  console.log(err.message); // some coding error in handling happened
});

4
Ви (і вищевказані коментарі) мали рацію. Мій route.handler.promiseHandler потребував .catch () та повернення помилки. Мені також потрібно було додати кінцевий .catch () до кінця ланцюга. Дякуємо, що передаєте важливість того, що на кожному кроці ланцюга є обробник успіху / помилок :).
Джон

2
Я також з’ясував, що якщо я кину помилку у свій .catch () для route.handler.promiseHandler, він автоматично перейде до остаточного улову. Якщо я поверну помилку замість цього, вона зробить те, що я хочу, і обробляє весь масив.
Джон

2
Зараз існує стандартний метод Promise.allSettled()з гідною підтримкою. Дивіться посилання .
Андреа Моугарс

Так, Promise.allне вдається, коли виходить з ладу перша нитка. Але, на жаль, всі інші потоки все ще продовжують працювати, поки вони не закінчуються. Ніщо не скасовується, ще гірше: немає способу скасувати тему в Promise. Отже, що б нитки не робили (і маніпулювали), вони продовжуються, вони змінюють стани та змінні, використовують процесор, але в кінці вони не повертають результат. Ви повинні знати про це, щоб не створювати хаосу, наприклад, при повторному / повторному виклику.
Марк Векерлін

143

НОВИЙ ВІДПОВІДЬ

const results = await Promise.all(promises.map(p => p.catch(e => e)));
const validResults = results.filter(result => !(result instanceof Error));

FUTURE Promise API


11
Хоча eце не повинно бути Error. Це може бути рядок, наприклад, якщо хтось поверне її так Promise.reject('Service not available').
Клесун

@ArturKlesun, як тоді ми могли класифікувати, яка з обіцянок призвела до помилки, а яка не?
Shubham Jain

5
@ shubham-jain з .then()і .catch(). Promise.resolve()передасть значення першому, тоді як Promise.reject()передасть його другому. Ви можете обернути їх в об'єкт, наприклад: p.then(v => ({success: true, value: v})).catch(e => ({success: false, error: e})).
Клесун

2
Навіщо фільтрувати результати? Це не має сенсу, якщо ви щось робите з результатами - вам потрібно замовлення, щоб знати, яка повертається вартість, з якої обіцянки!
Райан Тейлор

21

Для продовження Promise.allциклу (навіть коли Обіцянка відхиляється) я написав функцію утиліти, яка викликається executeAllPromises. Ця функція утиліти повертає об’єкт із resultsта errors.

Ідея полягає в тому, що всі Обіцянки, які ви передаєте, executeAllPromisesбудуть загорнуті в нове Обіцяння, яке завжди вирішуватиметься. Нова Promise вирішує масив, який має 2 плями. Перша пляма містить вирішальне значення (якщо таке є), а друге місце зберігає помилку (якщо відмовлена ​​Обіцянка відхиляє).

На завершальному етапі executeAllPromisesнакопичуються всі значення обернених обіцянок і повертається кінцевий об'єкт з масивом for resultsта масивом for errors.

Ось код:

function executeAllPromises(promises) {
  // Wrap all Promises in a Promise that will always "resolve"
  var resolvingPromises = promises.map(function(promise) {
    return new Promise(function(resolve) {
      var payload = new Array(2);
      promise.then(function(result) {
          payload[0] = result;
        })
        .catch(function(error) {
          payload[1] = error;
        })
        .then(function() {
          /* 
           * The wrapped Promise returns an array:
           * The first position in the array holds the result (if any)
           * The second position in the array holds the error (if any)
           */
          resolve(payload);
        });
    });
  });

  var errors = [];
  var results = [];

  // Execute all wrapped Promises
  return Promise.all(resolvingPromises)
    .then(function(items) {
      items.forEach(function(payload) {
        if (payload[1]) {
          errors.push(payload[1]);
        } else {
          results.push(payload[0]);
        }
      });

      return {
        errors: errors,
        results: results
      };
    });
}

var myPromises = [
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.reject(new Error('3')),
  Promise.resolve(4),
  Promise.reject(new Error('5'))
];

executeAllPromises(myPromises).then(function(items) {
  // Result
  var errors = items.errors.map(function(error) {
    return error.message
  }).join(',');
  var results = items.results.join(',');
  
  console.log(`Executed all ${myPromises.length} Promises:`);
  console.log(`— ${items.results.length} Promises were successful: ${results}`);
  console.log(`— ${items.errors.length} Promises failed: ${errors}`);
});


2
Це можна зробити простіше. Див stackoverflow.com/a/36115549/918910
гусек

18

ES2020 представляє новий метод для типу Promise: Promise.allSettled()
Promise.allSettled подає сигнал, коли всі вхідні обіцянки будуть виконані, а значить, вони або виконані, або відхилені. Це корисно у випадках, коли вам не байдуже стан обіцянки, ви просто хочете дізнатися, коли робота виконана, незалежно від того, чи вдала вона.

const promises = [
  fetch('/api-call-1'),
  fetch('/api-call-2'),
  fetch('/api-call-3'),
];
// Imagine some of these requests fail, and some succeed.

const result = await Promise.allSettled(promises);
console.log(result.map(x=>s.status));
// ['fulfilled', 'fulfilled', 'rejected']

Детальніше читайте у публікації блогу v8 https://v8.dev/features/promise-combinators


13

Як сказав @jib,

Promise.all є все або нічого.

Хоча ви можете контролювати певні обіцянки, які "дозволено" провалюватися, і ми хотіли б продовжувати .then.

Наприклад.

  Promise.all([
    doMustAsyncTask1,
    doMustAsyncTask2,
    doOptionalAsyncTask
    .catch(err => {
      if( /* err non-critical */) {
        return
      }
      // if critical then fail
      throw err
    })
  ])
  .then(([ mustRes1, mustRes2, optionalRes ]) => {
    // proceed to work with results
  })

6

якщо ви користуєтеся бібліотекою q https://github.com/kriskowal/q, у неї є метод q.allSettled (), який може вирішити цю проблему, ви можете впоратися з усіма обіцянками залежно від її стану або повною чи відхиленою так

existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
  return route.handler.promiseHandler();
});
return q.allSettled(arrayOfPromises)
});

existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
//so here you have all your promises the fulfilled and the rejected ones
// you can check the state of each promise
arrayResolved.forEach(function(item){
   if(item.state === 'fulfilled'){ // 'rejected' for rejected promises
     //do somthing
   } else {
     // do something else
   }
})
// do stuff with my array of resolved promises, eventually ending with a res.send();
});

Оскільки ви пропонуєте використовувати деяку бібліотеку ( q), було б корисніше, якби ви подали приклад використання, пов’язаний із цим питанням. На даний момент ваша відповідь не пояснює, як ця бібліотека може допомогти вирішити проблему.
ishmaelMakitla

додав приклад, як було запропоновано
Мохамед Махмуд

1
Приблизно у 2018 році завжди слід побачити, що доступно для Сіндре :-). github.com/sindresorhus/p-settle . З модулями єдиного призначення Sindre вам не потрібно імпортувати величезну бібліотеку, наприклад q, лише для одного біта.
ДКеблер

6

Використання Async wait -

тут одна функція асинхронізації func1 повертає розв'язане значення, а func2 кидає помилку і повертає нуль у цій ситуації, ми можемо обробляти її як хочемо і відповідно повертати.

const callingFunction  = async () => {
    const manyPromises = await Promise.all([func1(), func2()]);
    console.log(manyPromises);
}


const func1 = async () => {
    return 'func1'
}

const func2 = async () => {
    try {
        let x;
        if (!x) throw "x value not present"
    } catch(err) {
       return null
    }
}

callingFunction();

Вихід - ['func1', null]


4

Для тих, хто використовує ES8, що тут натрапляє, ви можете зробити щось на зразок наступного, використовуючи функції асинхронізації :

var arrayOfPromises = state.routes.map(async function(route){
  try {
    return await route.handler.promiseHandler();
  } catch(e) {
    // Do something to handle the error.
    // Errored promises will return whatever you return here (undefined if you don't return anything).
  }
});

var resolvedPromises = await Promise.all(arrayOfPromises);

3

Ми можемо впоратися з відхиленням на рівні індивідуальних обіцянок, тому коли ми отримаємо результати в нашому масиві результатів, індекс масиву, який був відхилений, буде undefined. Ми можемо впоратися з цією ситуацією за потребою та використати результати, що залишилися.

Тут я відкинув першу обіцянку, тому вона стає невизначеною, але ми можемо використовувати результат другої обіцянки, яка знаходиться в індексі 1.

const manyPromises = Promise.all([func1(), func2()]).then(result => {
    console.log(result[0]);  // undefined
    console.log(result[1]);  // func2
});

function func1() {
    return new Promise( (res, rej) => rej('func1')).catch(err => {
        console.log('error handled', err);
    });
}

function func2() {
    return new Promise( (res, rej) => setTimeout(() => res('func2'), 500) );
}


Як ви можете зробити подібну річ, якщо ми використовуємо async очікуємо?
Rudresh Ajgaonkar

Я відповів на ваше запитання, будь ласка, знайдіть посилання для відповіді. stackoverflow.com/a/55216763/4079716
Nayan Patel

2

Ви розглядали Promise.prototype.finally() ?

Схоже, він створений для того, щоб робити саме те, що ви хочете - виконайте функцію, коли всі обіцянки будуть укладені (вирішені / відхилені), незалежно від того, які обіцянки були відхилені.

З документації MDN :

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

finally()Метод дуже схожий на покликання.then(onFinally, onFinally) , проте є кілька відмінностей:

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

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

На відміну від Promise.resolve(2).then(() => {}, () => {})(який буде вирішено з невизначеним), Promise.resolve(2).finally(() => {})буде розв'язано з 2. Аналогічно, на відміну від Promise.reject(3).then(() => {}, () => {})(який буде виконано з невизначеним), Promise.reject(3).finally(() => {})буде відхилено з 3.

== Відступ ==

Якщо ваша версія JavaScript не підтримує, Promise.prototype.finally()ви можете скористатися цим способом вирішення від Jake Archibald :Promise.all(promises.map(p => p.catch(() => undefined)));


1
Так, поки Promises.allSettled()реально не буде впроваджено (це документально підтверджено MDN тут ), тоді Promises.all.finally(), здавалося б, можна зробити те саме. Я збираюся спробувати це ...
jamess

@jamess Чому ви не зробите цей коментар як належну відповідь? Жодна з відповідей не стосується ES6 allSettled().
pravin

@pravin - З того, що я можу сказати, allSettled()він ніде не застосовується (поки що), тому я не хочу випереджати реальність. У мене був успіх Promises.all(myPromiseArray).finally(), і це відповідає цій відповіді. Як тільки allSettled()насправді існує, я можу перевірити його і дізнатися, як він насправді працює. До тих пір, хто знає, що насправді реалізуватимуть браузери? Якщо у вас є недавня інформація про протилежне ...
jamess

@jamess Правда, що його все ще в стадії проектування. Однак останні FF і хром, здається, підтримують його повністю .. Не впевнений у стабільності цього .. Mozilla Docs Як би то не було, я намагався зробити, що це було б набагато простіше знайти якщо це була відповідь, ніж коментар .. це ур зателефонує хоч :)
pravin

@pravin - На той момент, коли я публікував свій коментар, він ніде не був реалізований. Я щойно тестував у Firefox та Chrome: Promise.allSettledне реалізований у Firefox, але, схоже, він існує в Chrome. Тільки тому, що документи кажуть, що він реалізований, не означає, що він реально реалізований. Я не збираюсь скоріше його використовувати.
jamess

0

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

function promiseNoReallyAll (promises) {
  return new Promise(
    async (resolve, reject) => {
      const failedPromises = []

      const successfulPromises = await Promise.all(
        promises.map(
          promise => promise.catch(error => {
            failedPromises.push(error)
          })
        )
      )

      if (failedPromises.length) {
        reject(failedPromises)
      } else {
        resolve(successfulPromises)
      }
    }
  )
}

0

Ви завжди можете обернути свої функції повернення обіцянки таким чином, що вони виявляють невдачу та повертають замість неї узгоджене значення (наприклад, помилка. Повідомлення), тому виняток не буде виконуватись до функції Promise.all та відключати її.

async function resetCache(ip) {

    try {

        const response = await axios.get(`http://${ip}/resetcache`);
        return response;

    }catch (e) {

        return {status: 'failure', reason: 'e.message'};
    }

}

0

Я знайшов спосіб (обхідний спосіб) зробити це без синхронізації.

Отже, як уже згадувалося, Promise.allце все ніщо.

так що ... Використовуйте обіцянку, що охоплює, і заставити рішення.


      let safePromises = originalPrmises.map((imageObject) => {
            return new Promise((resolve) => {
              // Do something error friendly
              promise.then(_res => resolve(res)).catch(_err => resolve(err))
            })
        })
    })

    // safe
    return Promise.all(safePromises)

0

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

try {
  let resArray = await Promise.all(
    state.routes.map(route => route.handler.promiseHandler().catch(e => e))
  );

  // in catch(e => e) you can transform your error to a type or object
  // that makes it easier for you to identify whats an error in resArray
  // e.g. if you expect your err objects to have e.type, you can filter
  // all errors in the array eg
  // let errResponse = resArray.filter(d => d && d.type === '<expected type>')
  // let notNullResponse = resArray.filter(d => d)

  } catch (err) {
    // code related errors
  }

0

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

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

const results = await Promise.all([
  this.props.client.query({
    query: GET_SPECIAL_DATES,
  }),
  this.props.client.query({
    query: GET_SPECIAL_DATE_TYPES,
  }),
  this.props.client.query({
    query: GET_ORDER_DATES,
  }),
]).catch(e=>console.log(e,"error"));
const specialDates = results[0].data.specialDates;
const specialDateTypes = results[1].data.specialDateTypes;
const orderDates = results[2].data.orders;

-1

Ось так Promise.allпокликано працювати. Якщо однієї обіцянки немає reject(), весь метод негайно провалюється.

Існують випадки використання, коли, можливо, хочеться, щоб Promise.allобіцянки провалилися. Щоб цього не сталося, просто не використовуйте жодних reject()тверджень у своїй обіцянці. Однак, щоб ваш додаток / сценарій не замерзав у випадку, якщо жодна основна обіцянка ніколи не отримає відповіді, вам потрібно поставити тайм-аут.

function getThing(uid,branch){
    return new Promise(function (resolve, reject) {
        xhr.get().then(function(res) {
            if (res) {
                resolve(res);
            } 
            else {
                resolve(null);
            }
            setTimeout(function(){reject('timeout')},10000)
        }).catch(function(error) {
            resolve(null);
        });
    });
}


Не використовувати reject()у своїй обіцянці чудово, але що робити, якщо вам потрібно використовувати обіцянки іншої бібліотеки?
Дан Даскалеску

-8

Я написав бібліотеку npm, щоб вирішити цю проблему красивіше. https://github.com/wenshin/promiseallend

Встановити

npm i --save promiseallend

2017-02-25 нові api, це не порушує обіцянки принципів

const promiseAllEnd = require('promiseallend');

const promises = [Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)];
const promisesObj = {k1: Promise.resolve(1), k2: Promise.reject('error'), k3: Promise.resolve(2)};

// input promises with array
promiseAllEnd(promises, {
    unhandledRejection(error, index) {
        // error is the original error which is 'error'.
        // index is the index of array, it's a number.
        console.log(error, index);
    }
})
    // will call, data is `[1, undefined, 2]`
    .then(data => console.log(data))
    // won't call
    .catch(error => console.log(error.detail))

// input promises with object
promiseAllEnd(promisesObj, {
    unhandledRejection(error, prop) {
        // error is the original error.
        // key is the property of object.
        console.log(error, prop);
    }
})
    // will call, data is `{k1: 1, k3: 2}`
    .then(data => console.log(data))
    // won't call
    .catch(error => console.log(error.detail))

// the same to `Promise.all`
promiseAllEnd(promises, {requireConfig: true})
    // will call, `error.detail` is 'error', `error.key` is number 1.
    .catch(error => console.log(error.detail))

// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [false, true, false]})
    // won't call
    .then(data => console.log(data))
    // will call, `error.detail` is 'error', `error.key` is number 1.
    .catch(error => console.log(error.detail))

// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [true, false, false]})
    // will call, data is `[1, undefined, 2]`.
    .then(data => console.log(data))
    // won't call
    .catch(error => console.log(error.detail))

——————————————————————————————————

Старі погані апі, не вживайте!

let promiseAllEnd = require('promiseallend');

// input promises with array
promiseAllEnd([Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)])
    .then(data => console.log(data)) // [1, undefined, 2]
    .catch(error => console.log(error.errorsByKey)) // {1: 'error'}

// input promises with object
promiseAllEnd({k1: Promise.resolve(1), k2: Promise.reject('error'), k3: Promise.resolve(2)})
    .then(data => console.log(data)) // {k1: 1, k3: 2}
    .catch(error => console.log(error.errorsByKey)) // {k2: 'error'}

Як це працює? Будь ласка, покажіть та поясніть свою функцію.
Бергі

Я написав нову паралельну логіку на кшталт Promise.all. Але він збиратиме всі дані та помилки кожної обіцянки. також він підтримує введення об'єкта, це не сенс. після збору всіх даних та помилок я заміняю promise.thenметод боротьби з зареєстрованими зворотними дзвінками, які включають відхилені та виконані. Докладно ви можете побачити код
wenshin

Е, цей код буде викликати onFulfilledі onRejectedобробників, які передаються then?
Бергі

Так, тільки тоді, коли обіцяють змішування статусу fulfilledта rejected. Але насправді це спричинює важку проблему, сумісну з усіма випадками використання обіцянок, як, наприклад, onFulfilledі onRejectedвсі повертаються Promise.reject()або Promise.resolve(). Поки мені не зрозуміло, як це вирішити, чи хтось має кращу ідею? Найкраща відповідь на даний момент є в тому, що вона не може фільтрувати дані та помилки в середовищі браузера.
Веньшин

Чи потрібно нам встановлювати npm-модуль з менеджером пакетів pip python?
семистороння
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.