Promise 错误处理

Promise 为 js 的异步流程控制处理迈出了一大步。
但我一直没用好错误处理。

抛出 error

生成一个 Promise 实例有两种方式,一种是 new Promise 还有一种是直接 Promise.resolvePromise.reject
但在 Promise 中抛出错误只有 throwreject 之分。

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
// 创建实例时拒绝
new Promise((resolve, reject) => {
reject('reject error');
}).catch(console.log);

// 创建实例时抛出错误
new Promise((resolve, reject) => {
throw 'throw error';
}).catch(console.log);

// 直接拒绝实例
Promise.reject('reject error2')
.catch(console.log);

// 过程中拒绝实例
Promise.resolve()
.then(() => Promise.reject('reject error3'))
.catch(console.log);

// 过程中抛出错误
Promise.resolve()
.then(() => {
throw 'throw error2';
})
.catch(console.log);

rejectthrow 在 Promise 中其实没有区别,都统一被 catch 捕获。
但一些文章说 throw 会在调试的时候被 “全部异常” 断点捕获,而 reject 不会。
我测试后发现,new Promise 的时候 reject 也会被断下,但 Promise.reject 不会。

链中的错误

如果一个 Promise 链很长,错误会找到后续链中最近的一个 catch

1
2
3
4
5
6
7
8
9
10
11
Promise.resolve(1)
.then(a => console.log(1))
.then(a => console.log(2))
.then(a => Promise.reject('error'))
.then(a => console.log(3))
.then(a => console.log(4))
.catch(err => console.log('err1', err))
.then(a => console.log(5))
.then(a => console.log(6))
.catch(err => console.log('err2', err))
.then(() => console.log('all done'))

在这个链中 3,4 步骤会被跳过,但 catch 过后的 then 依然可以正常执行。
这是很多新手会忽略的一个问题。

未知的异常

第三方封装或者自己疏忽导致在 new Promise 之前就报错,这就尴尬了。
所以就有了 Promise.try 的概念,虽然目前还在 stage 1 阶段,不过社区早早的就各种版本的实现了。

假设我们有个第三方模块叫做 readfile 异步读取一个文件。

1
2
3
readfile('1.txt')
.then(data => console.log(data))
.catch(err => console.log(err))

如果 readfile 在创建 Promise 之前就报错,我们无法 catch 到错误,甚至会影响整个进程导致异常而退出。
借助 Promise.try 来和谐这个操作。

1
2
3
Promise.try(() => readfile('1.txt'))
.then(data => console.log(data))
.catch(err => console.log(err))

这样就完全在我们的掌握之中了。

这里提供一个 sindresorhus 大神实现的 Promise.try ponyfill 模块 p-try

Promise.all

在异步并发的时候,Promise.all 是我们最常用的方式。
但如果 all 数组中有一个 Promise 实例异常了,会导致全部结果被丢弃。

1
2
3
4
5
6
7
8
9
10
11
const tasks = [
Promise.resolve(1),
Promise.resolve(2),
Promise.reject(3),
Promise.resolve(4),
Promise.resolve(5),
];

Promise.all(tasks)
.then(arr => console.log(arr))
.catch(err => console.log(err))

这个例子中,我们只能 catch 错误 3,其他结果直接被丢弃了。
虽然其他异步任务都执行了,但依然全部丢弃。
如果这是个 ajax 例子,5个 ajax 任务依然会全部执行,但只要一个错误,就全部丢弃。
其实我们会需要剩下的4个结果,错误的结果我们会提示用户或者重新请求。
所以我们要改造下。

1
2
3
4
5
6
7
8
9
10
11
const tasks = [
Promise.resolve(1),
Promise.resolve(2),
Promise.reject(3).catch(e => e),
Promise.resolve(4),
Promise.resolve(5),
];

Promise.all(tasks)
.then(arr => console.log(arr))
.catch(err => console.log(err))

就这么简单,捕获到错误后直接返回,而不是 throw/reject
但如果每个异步对象都需要手动 catch 其实也很麻烦,所以可以借助 map 处理。

1
2
3
4
5
6
7
8
9
10
11
const tasks = [
Promise.resolve(1),
Promise.resolve(2),
Promise.reject(3),
Promise.resolve(4),
Promise.resolve(5),
];

Promise.all(tasks.map(p => p.catch(e => e)))
.then(arr => console.log(arr))
.catch(err => console.log(err))

小结

这里分享了我使用 Promise 时候碰到的一些 错误处理 方面的经验。
其实也都是在各个大神的模块源码或博客中偷学来的。
键整理分享下,如果后续有新技巧,也会继续更新。