关于javascript:处理Promise.all中的错误

Handling errors in Promise.all

我有一个用Promise.all(arrayOfPromises)解决的Promises数组;

我继续继续诺言链。看起来像这样

1
2
3
4
5
6
7
8
9
10
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,以防万一出错,但是当我尝试时,Promise.all返回它找到的第一个错误(忽略其余错误),然后我无法从其余错误中获取数据数组中的promise(没有错误)。

我尝试做类似..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
});

但这并不能解决。

谢谢!

-

编辑:

下面的答案完全正确,但代码由于其他原因而中断。如果有人感兴趣,这就是我最终得到的解决方案...

节点快速服务器链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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调用)

1
2
3
4
5
6
7
8
9
return async()
    .then(function(result) {
        // dispatch a success
        return result;
    })
    .catch(function(err) {
        // dispatch a failure and throw error
        throw err;
    });

将.catch for Promise.all放在.then之前的目的似乎是捕获原始诺言中的任何错误,然后将整个数组返回到下一个.then。

谢谢!


Promise.all是全有还是全无。一旦阵列中的所有承诺都解决,它就会解决,或者一旦其中一个拒绝,就立即拒绝。换句话说,它要么使用所有已解析值的数组进行解析,要么使用单个错误进行拒绝。

有些库中有一个名为Promise.when的东西,据我所知,它会等待数组中的所有promise解析或拒绝,但是我对此并不熟悉,而且在ES6中也不是。

您的密码

我在这里同意其他人的观点,认为您的修复应该可以进行。它应使用可能包含成功值和错误对象混合的数组来解析。在成功路径中传递错误对象是不寻常的,但是假设您的代码期望它们,我认为它没有问题。

我能想到它"无法解决"的唯一原因是,它在代码中失败,您没有向我们显示;而您没有看到有关此消息的任何错误消息,是因为该诺言链没有以final结尾捕获(就您向我们展示的内容而言)。

我已自由地从您的示例中排除了"现有链",并以一条链来终止该链。这可能不适合您,但是对于阅读此书的人来说,始终返回或终止链很重要,否则潜在的错误(甚至编码错误)将被隐藏(这是我怀疑在这里发生的事情):

1
2
3
4
5
6
7
8
9
10
11
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
});


新答案

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

未来承诺API

  • Chrome 76:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
  • 您可以下载https://www.npmjs.com/package/promise.allsettled立即获取。在某些浏览器中,allSettled预先安装了浏览器本身。值得下载此软件包以省心,因为例如。 TypeScript没有allSettled的默认定义。


为了继续Promise.all循环(即使Promise拒绝),我编写了一个名为executeAllPromises的实用程序函数。该实用函数返回带有resultserrors的对象。

这个想法是,您传递给executeAllPromises的所有Promise都将被包装到一个新的Promise中,该Promise将始终解决。新的Promise分解为具有2个点的阵列。第一个点保留解析值(如果有),第二个点保留错误(如果包装的Promise拒绝)。

作为最后一步,executeAllPromises会累积已包装的Promise的所有值,并返回最终对象,该对象带有results的数组和errors的数组。

这是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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}`);
});


正如@jib所说,

Promise.all is all or nothing.

不过,您可以控制"允许"失败的某些承诺,我们希望继续进行.then

例如。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  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
  })

使用异步等待-

这里,一个异步函数func1返回一个解析值,而func2在这种情况下抛出一个错误并返回null,我们可以按需要处理它并相应地返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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]


如果您可以使用q库https://github.com/kriskowal/q
它具有q.allSettled()方法可以解决此问题
您可以根据承诺的状态来处理每个承诺,包括已履行或已拒绝
所以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();
});


对于使用ES8的用户,可以使用异步功能执行以下操作:

1
2
3
4
5
6
7
8
9
10
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级别处理拒绝,因此当我们在结果数组中获取结果时,已被拒绝的数组索引将为undefined。我们可以根据需要处理这种情况,并使用其余结果。

在这里,我拒绝了第一个承诺,因此它没有定义,但是我们可以使用第二个承诺的结果,即索引1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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) );
}


您是否考虑过Promise.prototype.finally()

它似乎旨在完全按照您的要求进行操作-一旦所有的诺言都已解决(解决/拒绝),就执行一个函数,而不管某些诺言是否被拒绝。

从MDN文档中:

如果您希望在兑现承诺后进行一些处理或清理,而不论其结果如何,则finally()方法将很有用。

finally()方法与调用.then(onFinally, onFinally)非常相似,但是有一些区别:

在创建函数内联时,您可以传递一次,而不必强迫其声明两次或为其创建变量。

由于没有可靠的方法来确定诺言是否已兑现,因此finally回调将不会收到任何参数。正是这种用例适用于您不关心拒绝原因或实现价值的情况,因此不需要提供它。

Promise.resolve(2).then(() => {}, () => {})(将使用undefined解析)不同,Promise.resolve(2).finally(() => {})将使用2解析。
同样,与Promise.reject(3).then(() => {}, () => {})(将使用undefined来实现)不同,Promise.reject(3).finally(() => {})将被3拒绝。

==后备==

如果您的JavaScript版本不支持Promise.prototype.finally(),则可以从Jake Archibald使用以下解决方法:Promise.all(promises.map(p => p.catch(() => undefined)));


您始终可以将诺言返回函数包装为能够捕获故障并返回约定值(例如error.message)的方式,因此异常不会一直累积到Promise.all函数并禁用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
async function resetCache(ip) {

    try {

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

    }catch (e) {

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

}

或者,如果您遇到的情况是当一个失败时您并不特别在意已解决的承诺的值,但是您仍然希望它们能够继续运行,那么您可以执行类似的操作,以在正常情况下以承诺解决问题他们全部成功,当其中任何一个失败时,就会以失败的承诺拒绝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)
      }
    }
  )
}


这就是Promise.all设计工作的方式。如果单个诺言reject(),则整个方法将立即失败。

在某些情况下,可能希望Promise.all允许诺言失败。为此,只需在您的promise中不使用任何reject()语句即可。但是,要确保您的应用程序/脚本不会冻结,以防万一任何基础承诺都不会得到响应,则需要在其上设置超时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
        });
    });
}


我写了一个npm库来更漂亮地处理这个问题。
https://github.com/wenshin/promiseallend

安装

1
npm i --save promiseallend

2017-02-25新的API,这不是违约原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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,请勿使用!

1
2
3
4
5
6
7
8
9
10
11
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'}