美文网首页WEB前端程序开发
且听前端大牛为你解析:Node.js内部捕获异步错误的步骤,看不

且听前端大牛为你解析:Node.js内部捕获异步错误的步骤,看不

作者: Web前端学习营 | 来源:发表于2019-04-12 19:17 被阅读0次

一、背景

众所周知,由于 JavaScript 特殊的 EventLoop 机制,由 Promise 异步产生错误是没有办法使用 try...catch 的:

try{Promise.reject()}catch(err){// 这里啥都 catch 不到console.log(err)}

为了解决这个问题,我们必须在每一处产生异步的地方使用 .catch()(或者用 async/await 搭配 try...catch):

DoSomethingAsync().then(...).catch(...)

但在实际工程里,总是会有一些 Promise 被遗漏掉,没有得到错误处理,在 Node.js 中这就会触发 unhandledRejection 事件,我们可以这样捕获未处理的 Promise 错误:

process.on('unhandledRejection',(reason,p)=>{console.log('Unhandled Rejection at:',p,'reason:',reason);});

这是 Node.js 的常识,也是常见的面试题之一,那么这个事件是如何被实现的呢?

二、unhandledRejection 的实现

如果你不想看技术细节的话,读懂下面两句话就够了:

V8 提供了接口(SetPromiseRejectCallback),当有未捕获的 Promise 错误时,会触发回调。Node.js 会在这个回调中记录下这些错误的 Promise 的信息;

Node.js 会在每次 Tick 执行完后检查是否有未捕获的错误 Promise,如果有,则触发 unhandledRejection 事件。

如果你想知道具体的代码实现,可以接着向下看……

️三、技术细节

我们以目前 Node.js 最新的 master 分支为例,首先,搜索代码可以找到,unhandledRejection在这一行(lib/internal/process/promises.js 139)被触发:

functionprocessPromiseRejections(){// ...letmaybeScheduledTicks=false;letlen=pendingUnhandledRejections.length;while(len--){constpromise=pendingUnhandledRejections.shift();constpromiseInfo=maybeUnhandledPromises.get(promise);if(promiseInfo!==undefined){promiseInfo.warned=true;const{reason,uid}=promiseInfo;if(!process.emit('unhandledRejection',reason,promise)){emitWarning(uid,reason);}maybeScheduledTicks=true;}}// ...}

processPromiseRejections() 这个函数在被调用时,会尝试读取 pendingUnhandledRejections这个数组,然后把里面存着的东西取出来,依次触发 unhandledRejection 事件。

那么就带来了两个问题:

是谁调用了这个函数让它触发unhandledRejection事件的?

是谁把有错误的 Promise 信息放进数组中的?

我们先解决第一个问题,通过搜索代码大法,我们可以找到 processPromiseRejections() 这个函数的调用链:

首先,processTicksAndRejections() 会在 tock queue 运行到空时,调用 processPromiseRejections(): lib/internal/process/task_queues.js 89

functionprocessTicksAndRejections(){lettock;do{// 运行Tock......}while(!queue.isEmpty()||processPromiseRejections());// ......}

然后,processTicksAndRejections() 这个函数被设置为每次 Tick 完成后的回调: lib/internal/process/task_queues.js 185

setTickCallback(processTicksAndRejections);

具体设置的方法,在 C++ 层是这里:src/node_task_queue.cc 45-49

staticvoidSetTickCallback(constFunctionCallbackInfo<Value>&args){Environment*env=Environment::GetCurrent(args);CHECK(args[0]->IsFunction());env->set_tick_callback_function(args[0].As<Function>());}

也就是说,每次 Tick 完成后,会触发 Tick 的回调,检查是不是有未处理的错误的 Promise,如果有,则会触发 unhandledRejection 事件。

然后是第二个问题,是谁把有错误的 Promise 信息放进数组中的?

同样是搜索代码大法,我们找到了这里:lib/internal/process/promises.js 31-64

functionpromiseRejectHandler(type,promise,reason){switch(type){casekPromiseRejectWithNoHandler:unhandledRejection(promise,reason);break;// ......}}functionunhandledRejection(promise,reason){//......pendingUnhandledRejections.push(promise);// ......}

这段代码里,promiseRejectHandler() 识别了传入的 Promise 和 Reject 的类型,如果类型符合,那么会调用 unhandledRejection() 向数组中加入这个没有错误处理但是已经报错的 Promise。

那么是谁向 promiseRejectHandler() 传入报错的 Promise 的呢?继续找:lib/internal/process/promises.js 148-150

functionlistenForRejections(){setPromiseRejectCallback(promiseRejectHandler);}

这里把 promiseRejectHandler() 设置为每次 Promise Reject 时的回调。

底层实现上,使用了 V8 提供的 SetPromiseRejectCallback() 这个接口:src/api/environment.cc 194

voidSetIsolateUpForNode(v8::Isolate*isolate){//......isolate->SetPromiseRejectCallback(task_queue::PromiseRejectCallback);//......}

然后在每次 Node.js 启动时,会有一个 setupTaskQueue() 的过程,在这个过程中,PromiseRejectCallback 被设置:lib/internal/process/task_queues.js 180-192

module.exports={setupTaskQueue(){// Sets the per-isolate promise rejection callbacklistenForRejections();//.....}};

四、知道这些有什么用?

1、第三方实现的 Promise 能触发 unhandledRejection 事件吗?

在上面已经说到,本质上 unhandledRejection 这个事件的实现还是依赖于 V8 实现的 Promise 对象以及对应的接口,也就是说如果我们使用了第三方实现的 Promise,就无法触发这个事件:

constPromise=require('bluebird')Promise.reject()process.on('unhandledRejection',(reason,p)=>{// 这里不会被触发,因为 Promise 不是原生实现的});

2、unhandledRejection 的回调是在何时被执行的?下面这段代码的输出是什么?

Promise.resolve().then(()=>console.log('p1'))Promise.reject()Promise.resolve().then(()=>{console.log('p2');process.nextTick(()=>{console.log('t3')Promise.resolve().then(()=>console.log('p3'))})})process.on('unhandledRejection',()=>{console.log('unhandledRejection')})

上面我们已经说到,每次 Tick 完成后,会执行并清空 Tock 队列,然后检查有没有异步错误,再触发 unhandledRejection 事件的回调。也就是说 unhandledRejection 的回调是在 Tick 和 Tock 队列都被清空之后进行,所以上面的输出应该是:

p1

p2

t3

p3

unhandledRejection

相关文章

  • 且听前端大牛为你解析:Node.js内部捕获异步错误的步骤,看不

    一、背景 众所周知,由于 JavaScript 特殊的 EventLoop 机制,由 Promise 异步产生错误...

  • 错误监控

    前端错误的分类 即时运行错误(代码错误) 资源加载错误 错误的调试方式 错误的捕获方式 即时运行错误的捕获方式 t...

  • 前端错误捕获

    常见错误的分类 对于用户在访问页面时发生的错误,主要包括以下几个类型: 1、js运行时错误 JavaScript代...

  • 前端捕获错误

    半个月过去了。。一个面试也没有。笔试倒是做了挺多,唉加油鸭 1. trycatch 判断一段代码是否存在异常 在异...

  • 前端错误收集脚本

    前端错误分为 JS 运行时错误、接口错误、资源加载错误三种。对于前端的异常捕获,我们都会知道 onerror 事件...

  • 前端监控

    前端错误可归纳为两种类型,捕获方式如下: 1.即时运行错误(代码错误) 2.资源加载错误 上报错误: 利用Imag...

  • 前端排序算法总结;前端面试题2.0;JavaScript异步编程

    前端排序算法总结;前端面试题2.0;JavaScript异步编程 标签(空格分隔): Node.js 1、前端 排...

  • 面试10:错误监控/产品性能体系

    课程思维导图 Q:前端错误分类有哪些? 即时运行错误,如代码错误 资源加载错误 Q:前端错误捕获方式分别是什么? ...

  • 错误监控

    一、前端错误的分类 即使运行错误(代码错误) 资源加载错误 二、错误的捕获方式 即使运行错误(代码错误) try...

  • JS错误监控 上报后台你了解多少?

    1.try-catch 总结: 只能捕获捉到运行时非异步错误,异步错误就显得无能为力,捕捉不到 2. window...

网友评论

    本文标题:且听前端大牛为你解析:Node.js内部捕获异步错误的步骤,看不

    本文链接:https://www.haomeiwen.com/subject/fwehwqtx.html