我在尝试实现一个等待数组的所有承诺并捕获所有错误的函数时发现了一个奇怪的问题。此实现导致由未处理的 Promise 拒绝导致的完整应用程序崩溃。
const completeAllPromises = async (promises) => {
const errors = []
for (let i = 0; i < promises.length; i++) {
await promises[i].catch((error) => {
error.message = `${i}: ${error.message}`
errors.push(error)
})
}
if (errors.length) return Promise.reject(errors)
}
const timeout = (duration) => {
return new Promise((resolve) => setTimeout(() => resolve()), duration)
}
const fail = async () => {
return Promise.reject(new Error('Failed'))
}
const crash = async () => {
await completeAllPromises([timeout(100), fail()]).catch(
(error) => console.log(`Crash prevented, catch promise rejection`, error), // never be called
)
}
const main = async () => {
// will crash and catch will not be called
await crash().catch((error) => console.log('Finished crash() and catch error', error))
}
main()
似乎函数中的 catch 实现completeAllPromises
是在运行时执行的,因此在等待第一个超时承诺时,fail()
将拒绝并且不会被捕获。对我来说奇怪的是crash()
and 也不会main()
捕获这个错误。似乎调用链在fail()
拒绝时无法解析。
切换timeout(100)
并fail()
会导致正确捕获错误。使用 try 和 catch 块也会导致同样的问题。
解决方案✅</h2>
const completeAllPromises = async (promises) => {
const errors = []
const results = await Promise.allSettled(promises)
results.forEach((result) => {
if (result.status === 'rejected') errors.push(result.reason)
})
if (errors.length) return Promise.reject(errors)
}
const timeout = (duration) => {
return new Promise((resolve) => setTimeout(() => resolve()), duration)
}
const fail = async () => {
return Promise.reject(new Error('Failed'))
}
const crash = async () => {
await completeAllPromises([timeout(100), fail()]).catch(
(error) => console.log(`Crash prevented, catch promise rejection`, error), // never be called
)
}
const main = async () => {
// will crash and catch will not be called
await crash().catch((error) => console.log('Finished crash() and catch error', error))
}
main()
const completeAllPromises = async (promises) => {
const errors = []
const results = await Promise.allSettled(promises)
results.forEach((result) => {
if (result.status === 'rejected') errors.push(result.reason)
})
if (errors.length) return Promise.reject(errors)
}
const timeout = (duration) => {
return new Promise((resolve) => setTimeout(() => resolve()), duration)
}
const fail = async () => {
return Promise.reject(new Error('Failed'))
}
const crash = async () => {
await completeAllPromises([timeout(100), fail()]).catch(
(error) => console.log(`Crash prevented, catch promise rejection`, error), // never be called
)
}
const main = async () => {
// will crash and catch will not be called
await crash().catch((error) => console.log('Finished crash() and catch error', error))
}
main()