promise 浏览器实现的源码_V8 中处理 Promise 的异步错误

本文探讨了在 V8 引擎中处理 Promise 的 unhandledRejection 事件的复杂性。通过研究 Node.js 源码,了解到 V8 的事件监听机制,包括 'kPromiseRejectWithNoHandler' 和 'kPromiseHandlerAddedAfterReject' 事件。Node.js 使用 pendingUnhandledRejections 数组并在每个事件周期结束时检查,以决定是否触发 'unhandledrejection'。文章还提出了利用微任务进行检查的解决方案,以适配 V8 的工作原理。
摘要由CSDN通过智能技术生成

1.背景

最初,是想处理在 V8 中正确处理 promise unhandledRejection

事实上,基于 V8 的 Chrome 和 Node 都提供了方法监听 unhandledRejection

// 浏览器
window.addEventListener("unhandledrejection", event => {
  console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});

// Node

process.on('unhandledRejection', (reason, promise) => {
    console.log('promise:', promise, 'reason:', reason);
});

最初我以为这是基于 V8 的简单封装,类似给 V8 加一个 listener,然后再把 unhandledRejection event 暴露给业务层,但是后来发现并不是,其中的过程比我想象的复杂。

2.初探

由于 V8 的文档很少,我基本上就是基于 Node 的源码结合搜索来找寻答案。最初,在 Isolate (V8 的一个类,需要了解可以自行 Google ) 中找到了一个 SetPromiseRejectCallback 的方法,我以为大功告成,于是开始编写代码

//js 测试代码
new Promise((resolve, reject)=>{
    throw 'unhandled rejection'
})


//c++ 处理代码
...
isolate->SetPromiseRejectCallback(PromiseRejectCallback)
...

void PromiseRejectCallbackJ2V8(PromiseRejectMessage message){
  if (event == kPromiseRejectWithNoHandler) {
    ...
  } else if (event == kPromiseHandlerAddedAfterReject) {
    ...
  }
}

编译,运行,发现确实可以收到 'kPromiseRejectWithNoHandler' 事件,很欣慰。

基于此,我又改了一下测试代码,如下

//js 测试代码
new Promise((resolve, reject)=>{
    throw 'unhandled rejection'
}).catch(error => {
})

再 run 一次,发现也能收到 'kPromiseRejectWithNoHandler' 事件,不过同时也会收到 'kPromiseHandlerAddedAfterReject' 事件,感觉事情隐隐约约起了变化。

基于 'kPromiseRejectWithNoHandler' 和 'kPromiseHandlerAddedAfterReject' 二者名字的简单推断

  • 当一个 Promise 变为 reject 状态的时候,会检查有没有 handler,如果没有,触发 'kPromiseRejectWithNoHandler'
  • 在 Promise 变为 reject 状态之后,再添加 handler,会触发 'kPromiseRejectWithNoHandler'

所以这边收到了两次事件。不过出于正常思维,显然,第二次的测试代码,不应该触发 'unhandledrejection', 那这个地方我们怎么过滤呢?

3. 方案

Node 源码中搜索 'SetPromiseRejectCallback',很 easy 的让我们找到了 Node 的实现机制。

这个找寻的过程可以看 @Starkwang 的大作,就不重复了

Starkwang:Node.js内部是如何捕获异步错误的?​zhuanlan.zhihu.com
6246c4d71b71d645d9653718a8d9ab91.png

简单归纳:

  • Node 会监听每次 'kPromiseRejectWithNoHandler' 并把 promise 放入一个数组 pendingUnhandledRejections 内
  • 收到 'kPromiseRejectWithNoHandler' 事件后,会把这个 promise 从 pendingUnhandledRejections 删除
  • 在每次 Ticks 执行完毕后,会检查 pendingUnhandledRejections 有没有剩余的 promise,如果有的话,触发 'unhandledRejection'

这里可能有一些不太好理解,为什么要每次 Ticks 执行完毕后去检查。

4.原理

首先,我们需要熟悉事件循环

Node.js 事件循环,定时器和 process.nextTick() | Node.js​nodejs.org
86b35010388a254e52698c7dbb064e84.png

在事件循环中,process.nextTick()从技术上讲不是事件循环的一部分,每次执行完一个事件(有些地方会称之为 宏任务),Ticks 会被执行。

在我们的场景中,也可以理解为 每次执行完一个事件(有些地方会称之为 宏任务),对于 'pendingUnhandledRejections' 的检查会发生。

也就是说,这里 Node 打了一个时间差。

如果一个 'kPromiseRejectWithNoHandler' 和 'kPromiseHandlerAddedAfterReject' 在同一个事件周期内触发,那么 'unhandledRejection' 会被触发,否则,不触发(Node 上会触发一个 'rejectionHandled' 事件)

好了,原理就是这样,不过这里有一个问题 Tick 是 Node 自己的一个概念,如果我们自己基于 V8 写代码的话,自然无法这么写。

我们的最初目的是想对一个事件周期触发的 'kPromiseRejectWithNoHandler' 和 'kPromiseHandlerAddedAfterReject' promise 进行去除,所以出于这个目的,只需要在像 Node 一样,在事件周期之间插入一个方法来检查。

最初我使用的方法是在每次接收到 'kPromiseRejectWithNoHandler' 事件的时候,构造一个 promise,在 promise 的回调函数去去检查。

为什么这么做呢?因为 promise 的回调函数是在微任务中执行的,而微任务就是在 事件周期之间 清空的,于是用这种比较 trick 的方法实现了。

//c++ 处理代码
...
isolate->SetPromiseRejectCallback(PromiseRejectCallback)
...

void PromiseRejectCallbackJ2V8(PromiseRejectMessage message){
  // 调用 js 的 promiseRejectHandler 方法
  ...
}



//js 处理代码
function promiseRejectHandler(type, promise, reason) {
  switch (type) {
    case 'kPromiseRejectWithNoHandler':
      unhandledRejection(promise, reason);
      break;
    ...
  }
}
function unhandledRejection(promise, reason) {
  ...
  // 在 promise 的回调函数去去检查
  Promise.resolve(1).then(value=>{
    processPromiseRejections();
  })
}

function processPromiseRejections(){
// 检查 
}

不过,其实 V8 提供了直接插入微任务的方式 'EnqueueMicrotask',所以其实我们也可以去掉这种 trick 的方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值