Обработка ошибок в обещании.все


у меня есть множество обещаний, которые я решаю с обещанием.все(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 для обработки отдельного обещания в случае его ошибок, но когда я пытаюсь, обещаю.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();
});

но это не решает.

спасибо!

--

Edit:

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

Node Express Server Chain

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 (маршрут.асинхронный вызов)

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

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

спасибо!

8 115

8 ответов:

Promise.all - это все или ничего. Он разрешает один раз все обещания в массиве resolve, или отклонить, как только один из них отвергает. Другими словами, он либо разрешает массив всех разрешенных значений, либо отклоняет с одной ошибкой.

В некоторых библиотеках есть что-то под названием 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
});

продолжить Promise.all цикл (даже когда обещание отклоняется) я написал функцию полезности, которая называется 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}`);
});

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

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

const Promise_all = promises => {
  return new Promise((resolve, reject) => {
    const results = [];
    let count = 0;
    promises.forEach((promise, idx) => {
      promise
        .catch(err => {
          return err;
        })
        .then(valueOrError => {
          results[idx] = valueOrError;
          count += 1;
          if (count === promises.length) resolve(results);
        });
    });
  });
};

const results = await Promise_all(promises)
const validResults = results.filter(result => !(result instanceof Error));

для тех, кто использует 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);

вот так Promise.all предназначен для работы. Если одно обещание reject() ' s, весь метод сразу терпит неудачу.

есть случаи использования, когда можно было бы иметь 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);
        });
    });
}

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

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

var 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) => { res("func2");}).catch(err => {
        console.log(`error handled ${err}`);
    });
}

Я написал библиотеку 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'}