iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > html >如何使用Node.js的Async Hooks模块追踪异步资源
  • 146
分享到

如何使用Node.js的Async Hooks模块追踪异步资源

2024-04-02 19:04:59 146人浏览 安东尼
摘要

小编给大家分享一下如何使用node.js的Async Hooks模块追踪异步资源,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!Async Hooks 功能是 node.js v8.x 版本

小编给大家分享一下如何使用node.js的Async Hooks模块追踪异步资源,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!

Async Hooks 功能是 node.js v8.x 版本新增加的一个核心模块,它提供了 api 用来追踪 Node.js  程序中异步资源的声明周期,可在多个异步调用之间共享数据

executionAsyncId 和 triggerAsyncId

async hooks 模块提供了 executionAsyncId() 函数标志当前执行上下文的异步资源 Id,下文使用 asyncId 表示。还有一个  triggerAsyncId() 函数来标志当前执行上下文被触发的异步资源 Id,也就是当前异步资源是由哪个异步资源创建的。每个异步资源都会生成  asyncId,该 id 会呈递增的方式生成,且在 Node.js 当前实例里全局唯一。

const asyncHooks = require('async_hooks'); const fs = require('fs'); const asyncId = () => asyncHooks.executionAsyncId(); const triggerAsyncId = () => asyncHooks.triggerAsyncId();  console.log(`Global asyncId: ${asyncHooks.executionAsyncId()}, Global triggerAsyncId: ${triggerAsyncId()}`);  fs.open('hello.txt', (err, res) => {   console.log(`fs.open asyncId: ${asyncId()}, fs.open triggerAsyncId: ${triggerAsyncId()}`); });

下面是我们运行的结果,全局的 asyncId 为 1,fs.open 回调里打印的 triggerAsyncId 为 1 由全局触发。

Global asyncId: 1, Global triggerAsyncId: 0 fs.open asyncId: 5, fs.open triggerAsyncId: 1

默认未开启的 Promise 执行跟踪

默认情况下,由于 V8 提供的 promise introspection API 相对消耗性能,Promise 的执行没有分配  asyncId。这意味着默认情况下,使用了 Promise 或 Async/Await 的程序将不能正确的执行和触发 Promise 回调上下文的  ID。即得不到当前异步资源 asyncId 也得不到当前异步资源是由哪个异步资源创建的 triggerAsyncId,如下所示:

Promise.resolve().then(() => {   // Promise asyncId: 0. Promise triggerAsyncId: 0   console.log(`Promise asyncId: ${asyncId()}. Promise triggerAsyncId: ${triggerAsyncId()}`); })

通过 asyncHooks.createHook 创建一个 hooks 对象启用 Promise 异步跟踪。

const hooks = asyncHooks.createHook({}); hooks.enable();  Promise.resolve().then(() => {   // Promise asyncId: 7. Promise triggerAsyncId: 6   console.log(`Promise asyncId: ${asyncId()}. Promise triggerAsyncId: ${triggerAsyncId()}`); })

异步资源的生命周期

asyncHooks 的 createHook() 方法返回一个用于启用(enable)和禁用(disable)hooks 的实例,该方法接收  init/before/after/destory 四个回调来标志一个异步资源从初始化、回调调用之前、回调调用之后、销毁整个生命周期过程。

init(初始化)

当构造一个可能发出异步事件的类时调用。

  • async:异步资源唯一 id

  • type:异步资源类型,对应于资源的构造函数名称,更多类型参考 async_hooks_type

  • triggerAsyncId:当前异步资源由哪个异步资源创建的异步资源 id

  • resource:初始化的异步资源

 init?(asyncId: number, type: string, triggerAsyncId: number, resource: object): void;

before(回调函数调用前)

当启动异步操作(例如 tcp 服务器接收新链接)或完成异步操作(例如将数据写入磁盘)时,系统将调用回调来通知用户,也就是我们写的业务回调函数。在这之前会先触发 before 回调。

 before?(asyncId: number): void;

after(回调函数调用后)

当回调处理完成之后触发 after 回调,如果回调出现未捕获异常,则在触发 uncaughtException 事件或域(domain)处理之后触发  after 回调。

 after?(asyncId: number): void;

destory(销毁)

当 asyncId 对应的异步资源被销毁后调用 destroy 回调。一些资源的销毁依赖于垃圾回收,因此如果对传递给 init  回调的资源对象有引用,则有可能永远不会调用 destory 从而导致应用程序中出现内存泄漏。如果资源不依赖垃圾回收,这将不会有问题。

 destroy?(asyncId: number): void;

promiseResolve

当传递给 Promise 构造函数的 resolve() 函数执行时触发 promiseResolve 回调。

 promiseResolve?(asyncId: number): void;

以下代码会触发两次 promiseResolve() 回调,第一次是我们直接调用的 resolve() 函数,第二次是在 .then()  里虽然我们没有显示的调用,但是它也会返回一个 Promise 所以还会被再次调用。

const hooks = asyncHooks.createHook({   promiseResolve(asyncId) {     syncLog('promiseResolve: ', asyncId);   } }); new Promise((resolve) => resolve(true)).then((a) => {});  // 输出结果 promiseResolve:  2 promiseResolve:  3

注意 init 回调里写日志造成 “栈溢出” 问题

一个异步资源的生命周期中第一个阶段 init 回调是当构造一个可能发出异步事件的类时会调用,要注意由于使用 console.log()  输出日志到控制台是一个异步操作,在 AsyncHooks 回调函数中使用类似的异步操作将会再次触发 init 回调函数,进而导致无限递归出现  RangeError: Maximum call stack size exceeded 错误,也就是 “ 栈溢出”。

调试时,一个简单的记录日志的方式是使用 fs.writeFileSync() 以同步的方式写入日志,这将不会触发 AsyncHooks 的 init  回调函数。

const syncLog = (...args) => fs.writeFileSync('log.txt', `${util.fORMat(...args)}\n`, { flag: 'a' }); const hooks = asyncHooks.createHook({   init(asyncId, type, triggerAsyncId, resource) {     syncLog('init: ', asyncId, type, triggerAsyncId)   } }); hooks.enable();  fs.open('hello.txt', (err, res) => {   syncLog(`fs.open asyncId: ${asyncId()}, fs.open triggerAsyncId: ${triggerAsyncId()}`); });

输出以下内容,init 回调只会被调用一次,因为 fs.writeFileSync 是同步的是不会触发 hooks 回调的。

init:  2 FSREQCALLBACK 1 fs.open asyncId: 2, fs.open triggerAsyncId: 1

异步之间共享上下文

Node.js v13.10.0 增加了 async_hooks 模块的 AsyncLocalStorage  类,可用于在一系列异步调用中共享数据。

如下例所示,asyncLocalStorage.run() 函数第一个参数是存储我们在异步调用中所需要访问的共享数据,第二个参数是一个异步函数,我们在  setTimeout() 的回调函数里又调用了 test2 函数,这一系列的异步操作都不影响我们在需要的地方去获取  asyncLocalStorage.run() 函数中存储的共享数据。

const { AsyncLocalStorage } = require('async_hooks'); const asyncLocalStorage = new AsyncLocalStorage(); asyncLocalStorage.run({ traceId: 1 }, test1); async function test1() {   setTimeout(() => test2(), 2000); } async function test2() {   console.log(asyncLocalStorage.getStore().traceId); }

AsyncLocalStorage 用途很多,例如在服务端必不可少的日志分析,一个 Http 从请求到响应整个系统交互的日志输出如果能通过一个  traceId 来关联,在分析日志时也就能够清晰的看到整个调用链路。

下面是一个 HTTP 请求的简单示例,模拟了异步处理,并且在日志输出时去追踪存储的 id

const http = require('http'); const { AsyncLocalStorage } = require('async_hooks'); const asyncLocalStorage = new AsyncLocalStorage(); function logWithId(msg) {   const id = asyncLocalStorage.getStore();   console.log(`${id !== undefined ? id : '-'}:`, msg); } let idSeq = 0; http.createServer((req, res) => {   asyncLocalStorage.run(idSeq++, () => {     logWithId('start');     setImmediate(() => {       logWithId('processing...');       setTimeout(() => {         logWithId('finish');         res.end();       }, 2000)     });   }); }).listen(8080);

下面是运行结果,我在第一次调用之后直接调用了第二次,可以看到我们存储的 id 信息与我们的日志一起成功的打印了出来。

如何使用Node.js的Async Hooks模块追踪异步资源

看完了这篇文章,相信你对“如何使用Node.js的Async Hooks模块追踪异步资源”有了一定的了解,如果想了解更多相关知识,欢迎关注编程网html频道,感谢各位的阅读!

--结束END--

本文标题: 如何使用Node.js的Async Hooks模块追踪异步资源

本文链接: https://www.lsjlt.com/news/80307.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何使用Node.js的Async Hooks模块追踪异步资源
    小编给大家分享一下如何使用Node.js的Async Hooks模块追踪异步资源,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!Async Hooks 功能是 Node.js v8.x 版本...
    99+
    2024-04-02
  • node 中如何使用Async异步处理模块
    今天就跟大家聊聊有关node 中如何使用Async异步处理模块,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Async异步处理模块!以下是小sam的...
    99+
    2024-04-02
  • node.js中如何使用async异步控制工具
    node.js中如何使用async异步控制工具,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。这两个操作中,第一个异步的程序我们可能会写成这...
    99+
    2024-04-02
  • 如何设置node.js模块和其下载资源的镜像
    这篇文章将为大家详细讲解有关如何设置node.js模块和其下载资源的镜像,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。后来研究发现 npm 不仅可以设置 node.js ...
    99+
    2024-04-02
  • Spring中的如何使用@Async异步调用
    这篇文章主要介绍了Spring中的如何使用@Async异步调用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。使用@Async异步调用方法Async简介异步方法调用使用场景:处...
    99+
    2023-06-25
  • Node.js的Process模块如何使用
    这篇文章主要介绍了Node.js的Process模块如何使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Node.js的Process模块如何使用文章都会有所收获,下面我们一起来看看吧。一、Process模块...
    99+
    2023-07-02
软考高级职称资格查询
推荐阅读
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作