当我只使用
onunhandledrejection
处理程序捕获它时,如何确定 Promise 拒绝发生的位置?
console.error = ()=>{}
window.addEventListener('unhandledrejection', (promiseRejectionEvent) => {
console.log('unhandled: ', Error().stack)
})
function main() {
new Promise(() => { throw null })
}
main()
如果您在运行此命令后检查浏览器的控制台,您将看到如下内容:
Error().stack
仅在其堆栈跟踪中包含拒绝处理函数本身(灰色输出js:14:30
)。但是浏览器确实似乎知道拒绝发生在哪里:还有另一个红色错误输出(Uncaught (in promise) null
),指向目标行(js:18
)。我如何访问此线路信息?
似乎后者的输出是由浏览器的内部完成的,因为它不能像上面的例子那样通过覆盖
console.error
来防止。正如 MDN所解释的那样,只能通过调用
promiseRejectionEvent.preventDefault()
来预防。但我不想阻止它,而是取而代之,例如用于记录目的。
现实世界的用例:当然可以不依赖
onunhandledrejection
事件处理程序,例如通过添加.catch()
短语或至少抛出throw new Error(null)
。但就我而言,我无法控制它,因为它是第三方代码。它今天意外地(可能是库错误)在客户端的浏览器中抛出,自动错误报告不包括堆栈跟踪。我试图缩小上面的潜在问题。谢谢!
编辑回应评论:
在 try/catch 中包装第三方代码? – weltschmerz
好点,但这无济于事,因为拒绝实际上发生在回调中:
window.addEventListener('unhandledrejection', (promiseRejectionEvent) => {
console.log('unhandled: ', Error().stack) // <- stack once again does *not* include "main()", it is only printed out in the console
})
function main() {
try {
thirdPartyModule()
} catch(e) {
// Never caught
console.log("caught:", e)
}
}
// Example code
// We cannot change this function
function thirdPartyModule() {
setTimeout(() =>
new Promise(() =>
{ throw null }))
}
main()
没有任何开箱即用的跟踪异步堆栈跟踪的好的解决方案,但可以使用 Zone.js 来做到这一点。如果您查看 Zone.js 页面上的演示,则有一个异步堆栈跟踪示例。
Zone 通过猴子修补所有创建异步任务的本机 API 来实现这一点。
免责声明:使用此解决方法对调试很有用,但对性能有轻微的负面影响,并可能导致各种库行为异常;它不应该在生产代码中使用。
您可以将 Promise 构造函数替换为您自己的实现,其中包括创建它的堆栈跟踪。
window.Promise = class FAKEPROMISE extends Promise {
constructor() {
super(...arguments);
this.__creationPoint = new Error().stack;
}
};
这将为您提供创建任何给定承诺的点的堆栈跟踪。 请注意,
.then
、.catch
和 .finally
都会创建新的承诺。
这不适用于由
async
函数创建的 Promises,因为它们不使用窗口的 Promise
构造函数。
这个可以通过阅读
promise
的PromiseRejectionEvent
成员来使用:
window.addEventListener('unhandledrejection', (promiseRejectionEvent) => {
console.log('unhandled: ', promiseRejectionEvent.promise.__creationPoint)
})
这将打印类似的东西:
unhandled: Error
at new FAKEPROMISE (script.js:4:32)
at main (script.js:6:3)
at script.js:9:1
不可能
我想你想要的“堆栈跟踪”将包括带有
throw null;
的行,但是,当调用unhandledrejection
事件处理程序时,它不在堆栈中。当
throw null;
被执行时,处理程序不会被直接(同步)调用,而是一个调用处理程序的微任务被排队。 (有关事件循环、任务和微任务的解释,请参阅 Jake Archibald 的“In The Loop”。)
这可以通过在抛出错误之前排队微任务来测试。如果 throwing 同步调用处理程序,微任务应该在它之后执行,但是如果 throw 队列中有一个调用处理程序的微任务,第一个微任务首先执行,然后是第二个微任务(调用处理程序)。
window.addEventListener('unhandledrejection', (promiseRejectionEvent) => {
console.log('unhandled: ', Error().stack) // <- stack once again does *not* include "main()", it is only printed out in the console
})
function main() {
try {
thirdPartyModule()
} catch (e) {
// Never caught
console.log("caught:", e)
}
}
// Example code
// We cannot change this function
function thirdPartyModule() {
setTimeout(() =>
new Promise(() => {
Promise.resolve().then(() => { // Queue a microtask before throwing
console.log("Microtask")
})
throw null
}))
}
main()
如您所见,我们的微任务首先执行,这意味着处理程序在微任务内部被调用。处理程序位于堆栈的顶部。
process.on('unhandledRejection', (reason, promise) => {
console.log('stackTrace:', reason.stack);
});
“原因”是一个本地错误对象。 您可以使用“stack”属性显示 stackTrace。