当前位置: 首页 > 后端技术 > Node.js

[转]学习使用Node.js中的async-hooks模块

时间:2023-04-03 12:09:53 Node.js

AsyncHooks是Node8的一个新特性。它提供了一些API用于跟踪NodeJs中异步资源的生命周期。它是Node的内置模块,可以直接引用:letasycnHooks=require('async_hooks');之所以引入async_hooks模块,是因为我们很难在异步调用中正确跟踪异步调用的处理逻辑和关系。async_hooks模块友好的解决了上述问题,主要提供了以下功能和特性:每个函数提供了一个context,我们称之为asyncscope;每个异步作用域都有一个asyncId,它是当前异步作用域的符号。一个async-scope中的asyncId必须相同,最外层asyncId为1,每次创建异步资源时asyncId全增;每个异步范围都有一个triggerAsyncId指示当前函数由该异步范围触发;通过asyncId和triggerAsyncId,我们可以方便的跟踪整个异步调用关系和链接;我们可以使用async_hooks.createHook函数来注册各个异步资源生命周期中发生的init/before/after/destory/promiseResolve等相关事件的监听函数;同一个asyncscope可能会被调用执行多次,无论执行多少次,它的asyncId都必须相同。通过监控功能,我们可以很方便地跟踪其执行的次数和时间以及在线关系;executionAsyncId和triggerAsyncIdasync_hooks模块提供了executionAsyncId函数和triggerAsyncId函数来获取当前上下文的asyncId和triggerAsyncId:constasync_hooks=require('async_hooks');constfs=require('fs');console.log('global.asyncId:',async_hooks.executionAsyncId());//global.asyncId:1console.log('global.triggerAsyncId:',async_hooks.triggerAsyncId());//global.triggerAsyncId:0fs.open('./app.js','r',(err,fd)=>{console.log('fs.open.asyncId:',async_hooks.executionAsyncId());//fs.open.asyncId:7console.log('fs.open.triggerAsyncId:',async_hooks.triggerAsyncId());//fs.open.triggerAsyncId:1});上面通过打印结果,可以看到全局asyncId为1,而fs.open回调函数的asyncId为7,triggerAsyncId为1。可见异步资源fs.open是由全球来电。async_hooks.createHook(callbacks)我们可以使用async_hooks.createHook为异步资源创建一个hook,将一些异步资源生命周期中可能发生的事件的回调函数注册为async_hooks.createHook的输入。只要创建/执行/销毁异步资源,就会触发这些钩子函数:constasync_hooks=require('async_hooks');constasyncHook=async_hooks.createHook({init(asyncId,type,triggerAsyncId,resource){},destroy(asyncId){}});asyncHook.enable();//通过enable函数开启hook函数目前createHook函数可以接受五种HookCallbacks如下:init(asyncId,type,triggerAsyncId,resource)init回调函数一般在异步资源初始化时触发,其参数解释如下:asyncId:每个异步资源都会生成一个唯一的标志type:异步资源的类型,通常是资源的构造函数的名称。如果想知道有哪些,可以参考async_hooks官方文档triggerAsyncId:表示触发创建当前异步资源的对应异步作用域。asyncIdresource:表示初始化后的异步资源对象before(asyncId)。before回调函数一般在asyncId对应的异步资源操作完成后,回调准备执行前被调用。beforecallbackA函数可能会被执行多次,由被回调的次数决定。after(asyncId)after回调函数一般在异步资源执行完回调函数后立即调用。如果回调函数执行过程中出现未捕获异常,则在触发“uncaughtException”事件后调用after事件。destroy(asyncId)在asyncId对应的异步资源被销毁时调用。一些异步资源的销毁依赖于垃圾回收机制,所以在某些情况下,由于内存泄漏,销毁事件可能永远不会被触发。promiseResolve(asyncId)当执行Promise构造函数中的resovle函数时,会触发promiseResolve事件。在某些情况下,一些解析函数是隐式执行的。比如.then函数会返回一个新的Promise,这个时候也会被调用。下面的表达式会触发两次promiseResolve事件,第一次是在执行newPromise()时显式执行的resolve函数,第二次是在.then函数的回调中隐式执行的resolve函数newPromise((解决)=>解决(真)).然后((a)=>{});Promise执行跟踪因为V8的PromiseIntrospectionAPI获取asyncId的执行成本比较高,所以默认情况下,我们不分配新的PromiseasyncId。也就是说,默认情况下,当我们使用promises或者async/await时,是无法获取到当前上下文正确的asyncId和triggerId的。不过没关系,我们可以通过执行async_hooks.createHook(callbacks).enable()函数强制将asyncId分配给Promise:constah=require('async_hooks');ah.createHook({init(){}}).enable();//PromiseHooks会被强制开启Promise.resolve(1729).then(()=>{console.log(`asyncId${ah.executionAsyncId()}triggerId${ah.triggerAsyncId()}`);});另外需要注意的是,before和after事件的钩子函数只有调用Promise链时才会被触发,即只会触发.then/.catch函数中生成的Promise,其他地方会只触发init和promiseResolve钩子事件函数。所以当我们执行如何操作时:newPromise((resolve)=>resolve(true)).then((a)=>{});分别触发的事件流程如下(假设两个Promise上下文对应的asyncId分别为5、6):1.initeventfunctionwithasyncId=52,promiseResolveeventfunctionwithasyncId=53,initeventfunctionwithasyncId=64,beforeeventfunctionwithasyncId=65,promiseResovleeventwithasyncId=6Function6,asyncId=6aftereventfunction异常处理如果AsyncHook回调函数出现异常,服务会打印错误日志并立即退出,所有'uncaughtException'监听器将被移除,'exit'事件将被触发。之所以进程会立即退出,是因为如果这些AsyncHook函数不稳定,可能会在下次触发相同事件时抛出异常。这些函数主要用于监控异步事件。如果不稳定,应及时发现并纠正。日志打印由于console.log函数也是异步调用,如果我们在asyncHook函数中再次调用console.log,相应的hook事件会再次触发,导致死循环调用,所以必须使用同步打印日志asyncHook函数中的方法跟踪,可以使用fs.writeSync函数:async_hooks.createHook({init(asyncId,type,triggerAsyncId){consteid=async_hooks.executionAsyncId();fs.writeSync(1,`${type}(${asyncId}):触发器:${triggerAsyncId}执行:${eid}\n`);}}).enable();参考async-hooks官方文档Node.jsv8.x新特性AsyncHook介绍