Обработка ошибок в Promise.all


267

У меня есть массив Обещаний, с которыми я разрешаю 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();
});

Я хочу добавить оператор catch для обработки отдельного обещания в случае ошибки, но когда я пытаюсь, 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)

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

Помещение .catchfor Promise.allперед тем, .thenкак кажется, послужило цели отлова любых ошибок из исходных обещаний, но затем возврата всего массива следующему.then

Спасибо!


2
Ваша попытка, похоже, должна сработать ... может быть, есть другая проблема где-то позже?
Ry-

.then(function(data) { return data; })может быть полностью опущен
Берги

Единственная причина, по которой вышеупомянутое не должно решаться, - это то, что если вы не показываете нам весь код в обработчиках thenили, catchи внутри выдается ошибка. Кстати, этот узел?

1
У вас нет окончательного улова в вашей «существующей цепочке», поэтому могут быть ошибки, которых вы не видите, которые могут объяснить, почему она «не разрешается». Попробуйте добавить это и посмотрите, какую ошибку вы получите.
Джиб

Ответы:


191

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. Таким образом, что бы потоки ни делали (и не манипулировали), они продолжают, они меняют состояния и переменные, они используют CPU, но в конце они не возвращают свой результат. Вы должны знать об этом, чтобы не создавать хаос, например, когда вы повторяете / повторяете вызов.
Марк Векерлин

144

НОВЫЙ ОТВЕТ

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

API БУДУЩЕГО Promise


11
Хотя eне должно быть Error. Это может быть строка, например, если кто-то возвращает ее как Promise.reject('Service not available').
Klesun

@ArturKlesun, как мы могли тогда классифицировать, какое из обещаний привело к ошибке, а какое нет?
Шубхам Джайн

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

2
Зачем вам фильтровать результаты? Это не имеет смысла, если вы что-то делаете с результатами - вам нужен порядок, чтобы узнать, какое возвращаемое значение является результатом какого обещания!
Райан Тейлор

21

Чтобы продолжить Promise.allцикл (даже когда Promise отклоняется), я написал служебную функцию, которая вызывается executeAllPromises. Эта служебная функция возвращает объект с resultsи errors.

Идея состоит в том, что все Обещания, которые вы передаете, executeAllPromisesбудут заключены в новое Обещание, которое всегда разрешается. Новое обещание разрешается с массивом, который имеет 2 места. Первое место содержит разрешающее значение (если оно есть), а второе - ошибку (если отклоненное обещание отклонено).

В качестве последнего шага команда executeAllPromisesнакапливает все значения упакованных обещаний и возвращает конечный объект с массивом для resultsи массивом для 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), было бы более полезно, если бы вы предоставили пример использования, связанный с вопросом. В своем нынешнем виде ваш ответ не объясняет, как эта библиотека может помочь решить проблему.
Ишмаэль Макитла

добавил пример в соответствии с предложением
Мохамед Махмуд

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

6

Использование Async await -

здесь одна асинхронная функция 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', ноль]


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 await?
Рудреш Айгаонкар

Я ответил на ваш вопрос, пожалуйста, найдите ссылку для ответа. stackoverflow.com/a/55216763/4079716
Наян Патель

2

Вы рассматривали Promise.prototype.finally()?

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

Из документации MDN :

Этот finally()метод может быть полезен, если вы хотите выполнить некоторую обработку или очистку после выполнения обещания независимо от его результата.

finally()Метод очень похож на вызов , .then(onFinally, onFinally)однако есть несколько отличий:

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

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

В отличие от Promise.resolve(2).then(() => {}, () => {})(который будет разрешен с помощью неопределенного), Promise.resolve(2).finally(() => {})будет разрешен с 2. Аналогично, в отличие Promise.reject(3).then(() => {}, () => {})(который будет выполнен с неопределенным), Promise.reject(3).finally(() => {})будет отклонен с 3.

== Откат ==

Если ваша версия JavaScript не поддерживает, Promise.prototype.finally()вы можете использовать этот обходной путь от Джейка Арчибальда :Promise.all(promises.map(p => p.catch(() => undefined)));


1
Да, до тех пор, пока Promises.allSettled()он фактически не будет реализован (это задокументировано MDN здесь ), тогда Promises.all.finally(), похоже, будет достигнуто то же самое. Я собираюсь дать ему попытку ...
Jamess

@jamess Почему бы вам не сделать этот комментарий как правильный ответ? Ни один из ответов не относится к ES6 allSettled().
Правин

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

@jamess Правда, он все еще находится в стадии черновика ... однако последние FF и хром, кажется, полностью его поддерживают .. Не уверен в его стабильности, хотя ... Mozilla Docs В любом случае, я пытался подчеркнуть, что найти его будет намного легче если это был ответ, а не комментарий .. хотя
ты звонишь

@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

Вы всегда можете обернуть свое обещание, возвращая функции таким образом, чтобы они улавливали сбой и возвращали вместо него согласованное значение (например, error.message), поэтому исключение не будет полностью свернуто до функции 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

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

Если вы используете 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))

--------------------------------

Старый плохой API, не используйте его!

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?
sevenfourk
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.