V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
iugo
V2EX  ›  JavaScript

Promise 的错误处理

  •  
  •   iugo · 2017-03-10 18:19:55 +08:00 · 4086 次点击
    这是一个创建于 2845 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    错误的抛出, 两种方法

    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

    15 条回复    2017-03-13 13:31:15 +08:00
    mopig
        1
    mopig  
       2017-03-10 18:32:23 +08:00
    > 因为 throw 并不会更改 promise 的状态

    这句话是啥意思,我测试好像是会改状态的。
    mcfog
        2
    mcfog  
       2017-03-10 18:36:00 +08:00
    感觉楼主基本没理解到点子上
    AlphaTr
        3
    AlphaTr  
       2017-03-10 18:37:28 +08:00 via iPhone
    throw 和 reject 理解错了, promise 会隐式对 throw 的错误 try catch 后 reject 掉
    m31271n
        4
    m31271n  
       2017-03-10 18:44:07 +08:00
    iugo
        5
    iugo  
    OP
       2017-03-10 19:11:57 +08:00
    @mopig 前面的话是 "网上有人建议". 我觉得不太合适.
    iugo
        6
    iugo  
    OP
       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
        7
    iugo  
    OP
       2017-03-10 19:25:59 +08:00
    @mcfog ... 我自己还觉得没问题. 请教槽点.
    mopig
        8
    mopig  
       2017-03-10 19:32:11 +08:00   ❤️ 1
    @iugo 你文章并没有纠正这个说法。。

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

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

    另外作为练习,建议你想想怎么用 promise 来完成一次递归。就比如说远程的分页拉信息的 api 如果不会返回总条数 /页数,只通过返回空列表标志列表结束,如何用返回 promise 的方法 getList(pageNo)来说构造一个拉完整列表的 getWholeList(),在第 n 页出错的时候用{currentPage:n, error:e}结构来 reject 你返回的 promise 对象
    iugo
        12
    iugo  
    OP
       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
        13
    iugo  
    OP
       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
        14
    mcfog  
       2017-03-13 12:53:41 +08:00 via Android
    @iugo 错误逻辑有点乱,且只处理了预料中的异常,另外, promise 的生成应该只出现一次就够了,在 new Promise 里面嵌套其他拿 Promise 的方法是典型的反 Promise 模式
    mcfog
        15
    mcfog  
       2017-03-13 13:31:15 +08:00   ❤️ 1
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1453 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 17:12 · PVG 01:12 · LAX 09:12 · JFK 12:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.