为何Promise#catch无法捕获setTimeout内部抛出的错误?
问题:为何加入setTimeout后Promise的catch无法捕获错误?
先看无延迟的原始函数:
function inc(x: number): Promise<number> { return new Promise(resolve => resolve(calculateIncrement(x))) } function calculateIncrement(x: number): number { if (x > 3) { throw new Error(`sorry, ${x} is too big`) } else { return x + 1 } }
调用时错误能被catch正常捕获:
>>> inc(2).then(y => console.log(`got ${y}`)).catch(e => console.log(`hmmm ... ${e}`)) got 3 >>> inc(4).then(y => console.log(`got ${y}`)).catch(e => console.log(`hmmm ... ${e}`)) hmmm ... sorry, 4 is too big
但加入延迟后,错误无法被catch捕获:
function incWithDelay(x: number, delayInMs: number): Promise<number> { return new Promise(resolve => setTimeout( () => resolve(calculateIncrement(x)), delayInMs ) ) }
调用结果:
>>> incWithDelay(2, 10).then(y => console.log(`got ${y}`)).catch(e => console.log(`hmmm ... ${e}`)) got 3 >>> incWithDelay(4, 10).then(y => console.log(`got ${y}`)).catch(e => console.log(`hmmm ... ${e}`)) Error: sorry, 4 is too big // <- 错误无法在此处被捕获
原因分析
核心问题在于代码执行的上下文差异和Promise的错误捕获机制:
- 无延迟版本中,
calculateIncrement(x)是在Promise构造函数的同步执行阶段调用的。此时抛出的错误会被Promise构造函数内部自动捕获,直接将Promise置为rejected状态,后续的catch自然能处理这个错误。 - 加入
setTimeout后,calculateIncrement(x)是在定时器的异步回调中执行的。这个回调属于独立的执行上下文,脱离了Promise构造函数的同步执行流程:- 当回调里的函数抛出错误时,Promise已经完成初始化(处于pending状态),这个错误会直接冒泡到全局上下文,成为未捕获的全局错误,无法被Promise的错误捕获机制感知。
- 错误会直接中断回调的执行,连
resolve都没机会调用,Promise会一直处于pending状态,catch自然不会被触发。
修复方案
要让错误能被Promise捕获,需要在定时器回调内部手动捕获错误,并调用reject改变Promise的状态:
function incWithDelay(x: number, delayInMs: number): Promise<number> { return new Promise((resolve, reject) => setTimeout(() => { try { resolve(calculateIncrement(x)); } catch (e) { reject(e); } }, delayInMs) ) }
也可以用更简洁的方式,用Promise包裹计算逻辑来自动捕获错误:
function incWithDelay(x: number, delayInMs: number): Promise<number> { return new Promise((resolve, reject) => { setTimeout(() => { Promise.resolve(calculateIncrement(x)).then(resolve).catch(reject); }, delayInMs); }); }
这样当calculateIncrement抛出错误时,会被捕获并调用reject(e),将Promise置为rejected状态,后续的catch就能正常处理错误了。
内容的提问来源于stack exchange,提问作者Sasgorilla
相关产品推荐
相关产品推荐

