k8w.io
Uncaught (in promise) 异常的解决办法
2019-10-30作者:k8w

Promiseasync/await 在流行JS编程中已经很常见了,但你有遇到过这个报错吗?

这篇文章为你深度分析此类问题的成因和解决办法。

例子

function getList(){
    return new Promise((rs,rj)=>{
        rj('假装发生了错误')
    })
}

try{
    getList();
}
catch(e){
    alert('遇到了些许错误哦~')
}

一看就是有经验的攻城狮,使用try…catch…捕获异常,发生错误时显示友好的提示。

emmm……可是实际会发生什么呢?

答案是,此处的异常将无法捕获到!
getList()抛出异常后,嗯,流程就失控了。

Promise的异常捕获方式

打开控制台会发现,上面的例子将会抛出 Uncaught (in promise)异常。

这里要提到Promise的异常处理流程。

Promise创建时需要传入一个function,在这个function执行过程中,如果出现了异常则会对外抛出。
外部有2种方式来捕获这个异常:

方式一:catch()

let promise = new Promise(...);
promise.catch(e=>{
    // TODO sth with e
})

方式二:async/await中的try...catch...

let promise = new Promise(...);
async function test(){
    try{
        await promise;
    }
    catch(e){
        // TODO sth with e
    }
}

全局异常

如果这两种方式都没有出现,则异常将会被视为 “Uncaught (in promise)” 被抛出到全局去。
在NodeJS中,你可以通过process.on('unhandledRejection', e => {...})来捕获全局异常。
遗憾的是,在浏览器中,尚未发现有效的方式能捕获此类全局异常。
所以应当极力避免出现全局异常的情况,尤其是在前端,可能由异常导致流程中断。

只要Promise的异常被方式一或方式二捕获,就不会抛出全局异常!

未捕获异常的原因

例子中的Promise异常未被正常捕获,是因为promise虽然出现在try...catch...中,但是并没有被await,如此将不进入上述的异常捕获流程,一旦出现异常并且没有其它有效的catch时,就将抛出至全局。

嗯,看起来似乎你已经了解了,那么把例子1按如下修改一下,你觉得是否可以正常工作呢?

function getList(){
    return new Promise((rs,rj)=>{
        rj('假装发生了错误')
    })
}

async function main(){
    try{
        getList();
    }
    catch(e){
        alert('遇到了些许错误哦~')
    }
}

main();

依旧不行!
因为Promise虽然出现在了async方法中,但由于没有被await,所以其异常视为全局异常。

此处有神坑!请注意!

new Promise(rs=>{
    throw new Error('Error')
}).catch(e=>{
    console.log('异常被捕获到了1')
})

new Promise(async rs=>{
    throw new Error('Error')
}).catch(e=>{
    console.log('异常被捕获到了2')
})

上面2个Promise里的异常,能被捕获到吗?

答案是第一个Promise里的异常能被捕获,因为Promise里同步抛出的异常,也会被视为Promise.reject。
但第二个Promise,由于里面的函数是async的,异常是异步抛出的,所以并不会触发Promise的reject,因此Promise.catch也就捕获不到。

怎么理解呢?换个方式可能好理解一些。
async函数本身,就是Promise的另一种写法,二者一定能够互相转化且等效。
转化成Promise的等效写法就是:

new Promise(rs=>{
    // async 相当于同步函数里又包了一层Promise
    return new Promise(()=>{
        // 内层Promise抛出异常
        throw new Error('Error')
    })
}).catch(e=>{  // 这里catch的是外层Promise
    // 由于异常并未向上抛给外层Promise,所以此处catch不到
    console.log('异常被捕获到了2')
})

如之前所述,Promise内抛出的异常,无论身在何处,只要未经捕获,就会直接上升为全局“未经捕获的异常”,而不是层层抛出。

小测试

嗯,如果你已经了解了这些重要的区别,那么用下面的例子测试一下吧~

看看下面的例子,getList()中的异常能被捕获到吗?

  • 如果能,将在哪个位置被捕获到?
  • 如果不能,又是为什么呢?

将你的答案留在评论处吧~~

async function getList(){
    new Promise((rs,rj)=>{
        rj('假装发生了错误')
    });
}

async function main(){
    try{
        await getList().catch(e=>{
            console.log('异常捕获到了,位置1')
        });
    }
    catch(e){
        console.log('异常捕获到了,位置2')
    }
}

main();
(正文完)
留言(2条)
小明 说:
异常不会被捕获。因为promise方法没有返回一个promise。
2019-11-01 16:04 | 1楼 | 回复
张三 说:
没搞懂最后的例子怎么无法捕获错误
2020-04-27 17:20 | 2楼 | 回复
发表新留言
您的大名:
必填
电子邮箱:
不公开,仅用于向你发送回复