Promise 的错误处理

2017-03-10 18:19:55 +08:00
 iugo

网上没找到很有收获的文章, 所以自己总结了一下:

错误的抛出, 两种方法

  1. reject()
  2. throw

两种方法效果基本是等同的, 区别在于 reject() 不会终止函数的执行.

只有最先抛出的错误会被捕获. 比如先 reject() 一个错误, 再 throw 第二个错误, 捕获到的是第一个错误, 但第二个抛出的错误也会被执行, 不过只有终止函数的作用了, 相当于 return.

网上有人建议不要使用 throw 抛出错误, 因为 throw 并不会更改 promise 的状态, 而且容易和系统抛出的其他异常混淆. 我认为这个建议值得继续探讨而不是下定论, 首先, promise 的状态并不能直接监控, 其次抛出什么错误我们完全可以扩展 Error 而区分错误类型.

我的建议是没有对错, 分具体情况使用就好.

reject() 的优点, 不影响函数继续执行, 没有 throw 的作用域限制.

throw 的优点, 不需要在 Promise 中, 在 then() 中的普通函数中也能抛出错误.

错误的捕获, 两种方法

Promise 内部抛出的错误是无法被外部的 try...catch 捕获的, 因为作用域问题.

  1. then() 的第二个参数
  2. catch() 的第一个参数

错误的处理都是调用一个函数 (onRejected), 传入一个参数, 这个参数即为刚才抛出的错误.

错误只会被捕获一次. 且如果错误被捕获, 则之后的 then() 都会被执行, 即使不拥有正常的返回值作为参数. 因此建议链式调用最末端再捕获错误.

这两种方法没有什么区别, 无论是如何抛出错误, 都能被正常捕获.


注: 使用的是 https://github.com/taylorhakes/promise-polyfill

原文: https://github.com/zsqk/zsqk/issues/51

4084 次点击
所在节点    JavaScript
15 条回复
mopig
2017-03-10 18:32:23 +08:00
> 因为 throw 并不会更改 promise 的状态

这句话是啥意思,我测试好像是会改状态的。
mcfog
2017-03-10 18:36:00 +08:00
感觉楼主基本没理解到点子上
AlphaTr
2017-03-10 18:37:28 +08:00
throw 和 reject 理解错了, promise 会隐式对 throw 的错误 try catch 后 reject 掉
m31271n
2017-03-10 18:44:07 +08:00
iugo
2017-03-10 19:11:57 +08:00
@mopig 前面的话是 "网上有人建议". 我觉得不太合适.
iugo
2017-03-10 19:25:23 +08:00
@AlphaTr 我只是用, 还没看过 polyfill 具体实现的代码. 我觉得我说的和 "promise 会隐式对 throw 的错误 try catch 后 reject 掉" 没有冲突.

代码例子:
```
// 适合使用 reject()
new Promise((resolve, reject) => {
someFunc({
cb() {
reject()
}
})
}).catch(eh)

// 适合使用 throw
new Promise(resolve => {
resolve(data)
}).then(data => {
const res = handle(data)
if (!res) {
throw new Error('error')
}
}).catch(eh)
```
iugo
2017-03-10 19:25:59 +08:00
@mcfog ... 我自己还觉得没问题. 请教槽点.
mopig
2017-03-10 19:32:11 +08:00
@iugo 你文章并没有纠正这个说法。。

还有你表述的更多的是一些使用习惯。
AlphaTr
2017-03-10 20:11:58 +08:00
@iugo 先说 throw 的优点,其实并不是在 promise 中的特性,而是 throw 本身的特性,而在一般使用中 return reject() 伴随着使用,常用场景就是遇到错误不继续执行,而 下面说的 在 then 中,同样可以使用 return Promise.reject() 达到 throw 同样的效果,所以结论就是这两种使用起来基本没有区别,可以互相替换着
AlphaTr
2017-03-10 20:13:35 +08:00
@iugo 少补充一点, throw 只能在同步场景中使用, reject 可以用于异步,可能就是两个的区别吧
mcfog
2017-03-10 21:56:19 +08:00
@iugo 首先可以去看看 promise/A+的全文,创建 promise 的方法也好,.catch 也好都不在标准范围内。 throw 和 reject 行为的差异也好, catch 后的 then 为什么继续运行也好,都在标准里有,虽然有点拗口 /晦涩,但却是直指核心的

如果你理解了的话就不会用”捕获错误“这样的描述来讲这个问题了

另外作为练习,建议你想想怎么用 promise 来完成一次递归。就比如说远程的分页拉信息的 api 如果不会返回总条数 /页数,只通过返回空列表标志列表结束,如何用返回 promise 的方法 getList(pageNo)来说构造一个拉完整列表的 getWholeList(),在第 n 页出错的时候用{currentPage:n, error:e}结构来 reject 你返回的 promise 对象
iugo
2017-03-13 11:29:42 +08:00
@mcfog 我写的都是使用经验, 没有看标准的定义.

ECMA 262 6th Promise Abstract Operations, Promise Jobs 让我感觉似懂非懂. 因为标准中有许多关联, 要把关联搞懂才能更好地继续阅读, 比如 Record, Reaction, Job 等等.

关于错误这块儿, throw 好像是 PromiseReaction[[Handler]] is "Thrower", handlerResult be Completion, 正常应该是 handlerResult be Call. 但我不确定我看懂了...

就我目前来说, 还是使用吧, 依靠经验...
iugo
2017-03-13 12:35:27 +08:00
@mcfog 做了一下练习题, 感觉实现了功能, 但没发现自己存在的问题...

```
const api = (num) => {
const allNum = 10;
return new Promise(resolve => {
setTimeout(() => {
if (num <= allNum) {
resolve([num])
} else {
resolve(false)
}
}, 100)
})
}

const getList = (num = 1) => {
return new Promise((resolve, reject) => {
api(num).then(res => {
if (res) {
resolve(res);
} else {
reject({
currentPage: num,
error: {
code: 9876,
message: 'can not find the page.'
}
});
}
})
})
}

const getWholeList = (num = 1, list = []) => {
return new Promise(resolve => {
getList(num).then(res => {
resolve(getWholeList(num + 1, [...list, ...res]))
}).catch(e => {
if (e.error.code === 9876) {
resolve(list)
}
})
})
}

getWholeList().then(res => {
console.log('完成', res)
})
```
mcfog
2017-03-13 12:53:41 +08:00
@iugo 错误逻辑有点乱,且只处理了预料中的异常,另外, promise 的生成应该只出现一次就够了,在 new Promise 里面嵌套其他拿 Promise 的方法是典型的反 Promise 模式
mcfog
2017-03-13 13:31:15 +08:00

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/346533

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX