自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用 Node.js 的 Async Hooks 模塊追蹤異步資源

開發(fā) 前端
Async Hooks 功能是 Node.js v8.x 版本新增加的一個核心模塊,它提供了 API 用來追蹤 Node.js 程序中異步資源的聲明周期,可在多個異步調(diào)用之間共享數(shù)據(jù),本文從最基本入門篇開始學(xué)習(xí),之后會有在某些場景下具體應(yīng)用實踐篇介紹。

[[378342]]

作者簡介:五月君,Software Designer,公眾號「Nodejs技術(shù)棧」作者。

Async Hooks 功能是 Node.js v8.x 版本新增加的一個核心模塊,它提供了 API 用來追蹤 Node.js 程序中異步資源的聲明周期,可在多個異步調(diào)用之間共享數(shù)據(jù),本文從最基本入門篇開始學(xué)習(xí),之后會有在某些場景下具體應(yīng)用實踐篇介紹。

executionAsyncId 和 triggerAsyncId

async hooks 模塊提供了 executionAsyncId() 函數(shù)標(biāo)志當(dāng)前執(zhí)行上下文的異步資源 Id,下文使用 asyncId 表示。還有一個 triggerAsyncId() 函數(shù)來標(biāo)志當(dāng)前執(zhí)行上下文被觸發(fā)的異步資源 Id,也就是當(dāng)前異步資源是由哪個異步資源創(chuàng)建的。每個異步資源都會生成 asyncId,該 id 會呈遞增的方式生成,且在 Node.js 當(dāng)前實例里全局唯一。

  1. const asyncHooks = require('async_hooks'); 
  2. const fs = require('fs'); 
  3. const asyncId = () => asyncHooks.executionAsyncId(); 
  4. const triggerAsyncId = () => asyncHooks.triggerAsyncId(); 
  5.  
  6. console.log(`Global asyncId: ${asyncHooks.executionAsyncId()}, Global triggerAsyncId: ${triggerAsyncId()}`); 
  7.  
  8. fs.open('hello.txt', (err, res) => { 
  9.   console.log(`fs.open asyncId: ${asyncId()}, fs.open triggerAsyncId: ${triggerAsyncId()}`); 
  10. }); 

下面是我們運(yùn)行的結(jié)果,全局的 asyncId 為 1,fs.open 回調(diào)里打印的 triggerAsyncId 為 1 由全局觸發(fā)。

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

默認(rèn)未開啟的 Promise 執(zhí)行跟蹤

默認(rèn)情況下,由于 V8 提供的 promise introspection API 相對消耗性能,Promise 的執(zhí)行沒有分配 asyncId。這意味著默認(rèn)情況下,使用了 Promise 或 Async/Await 的程序?qū)⒉荒苷_的執(zhí)行和觸發(fā) Promise 回調(diào)上下文的 ID。即得不到當(dāng)前異步資源 asyncId 也得不到當(dāng)前異步資源是由哪個異步資源創(chuàng)建的 triggerAsyncId,如下所示:

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

通過 asyncHooks.createHook 創(chuàng)建一個 hooks 對象啟用 Promise 異步跟蹤。

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

異步資源的生命周期

asyncHooks 的 createHook() 方法返回一個用于啟用(enable)和禁用(disable)hooks 的實例,該方法接收 init/before/after/destory 四個回調(diào)來標(biāo)志一個異步資源從初始化、回調(diào)調(diào)用之前、回調(diào)調(diào)用之后、銷毀整個生命周期過程。

init(初始化)

當(dāng)構(gòu)造一個可能發(fā)出異步事件的類時調(diào)用。

  • async:異步資源唯一 id
  • type:異步資源類型,對應(yīng)于資源的構(gòu)造函數(shù)名稱,更多類型參考 async_hooks_type
  • triggerAsyncId:當(dāng)前異步資源由哪個異步資源創(chuàng)建的異步資源 id
  • resource:初始化的異步資源
  1. /** 
  2.  * Called when a class is constructed that has the possibility to emit an asynchronous event. 
  3.  * @param asyncId a unique ID for the async resource 
  4.  * @param type the type of the async resource 
  5.  * @param triggerAsyncId the unique ID of the async resource in whose execution context this async resource was created 
  6.  * @param resource reference to the resource representing the async operation, needs to be released during destroy 
  7.  */ 
  8. init?(asyncId: number, type: string, triggerAsyncId: number, resource: object): void; 

before(回調(diào)函數(shù)調(diào)用前)

當(dāng)啟動異步操作(例如 TCP 服務(wù)器接收新鏈接)或完成異步操作(例如將數(shù)據(jù)寫入磁盤)時,系統(tǒng)將調(diào)用回調(diào)來通知用戶,也就是我們寫的業(yè)務(wù)回調(diào)函數(shù)。在這之前會先觸發(fā) before 回調(diào)。

  1. /** 
  2.  * When an asynchronous operation is initiated or completes a callback is called to notify the user
  3.  * The before callback is called just before said callback is executed. 
  4.  * @param asyncId the unique identifier assigned to the resource about to execute the callback. 
  5.  */ 
  6. before?(asyncId: number): void; 

after(回調(diào)函數(shù)調(diào)用后)

當(dāng)回調(diào)處理完成之后觸發(fā) after 回調(diào),如果回調(diào)出現(xiàn)未捕獲異常,則在觸發(fā) uncaughtException 事件或域(domain)處理之后觸發(fā) after 回調(diào)。

  1. /** 
  2.  * Called immediately after the callback specified in before is completed. 
  3.  * @param asyncId the unique identifier assigned to the resource which has executed the callback. 
  4.  */ 
  5. after?(asyncId: number): void; 

destory(銷毀)

當(dāng) asyncId 對應(yīng)的異步資源被銷毀后調(diào)用 destroy 回調(diào)。一些資源的銷毀依賴于垃圾回收,因此如果對傳遞給 init 回調(diào)的資源對象有引用,則有可能永遠(yuǎn)不會調(diào)用 destory 從而導(dǎo)致應(yīng)用程序中出現(xiàn)內(nèi)存泄漏。如果資源不依賴?yán)厥?,這將不會有問題。

  1. /** 
  2.  * Called after the resource corresponding to asyncId is destroyed 
  3.  * @param asyncId a unique ID for the async resource 
  4.  */ 
  5. destroy?(asyncId: number): void; 

promiseResolve

當(dāng)傳遞給 Promise 構(gòu)造函數(shù)的 resolve() 函數(shù)執(zhí)行時觸發(fā) promiseResolve 回調(diào)。

  1. /** 
  2.   * Called when a promise has resolve() called. This may not be in the same execution id 
  3.   * as the promise itself. 
  4.   * @param asyncId the unique id for the promise that was resolve()d. 
  5.   */ 
  6. promiseResolve?(asyncId: number): void; 

以下代碼會觸發(fā)兩次 promiseResolve() 回調(diào),第一次是我們直接調(diào)用的 resolve() 函數(shù),第二次是在 .then() 里雖然我們沒有顯示的調(diào)用,但是它也會返回一個 Promise 所以還會被再次調(diào)用。

  1. const hooks = asyncHooks.createHook({ 
  2.   promiseResolve(asyncId) { 
  3.     syncLog('promiseResolve: ', asyncId); 
  4.   } 
  5. }); 
  6. new Promise((resolve) => resolve(true)).then((a) => {}); 
  7.  
  8. // 輸出結(jié)果 
  9. promiseResolve:  2 
  10. promiseResolve:  3 

注意 init 回調(diào)里寫日志造成 “棧溢出” 問題

一個異步資源的生命周期中第一個階段 init 回調(diào)是當(dāng)構(gòu)造一個可能發(fā)出異步事件的類時會調(diào)用,要注意由于使用 console.log() 輸出日志到控制臺是一個異步操作,在 AsyncHooks 回調(diào)函數(shù)中使用類似的異步操作將會再次觸發(fā) init 回調(diào)函數(shù),進(jìn)而導(dǎo)致無限遞歸出現(xiàn) RangeError: Maximum call stack size exceeded 錯誤,也就是 “ 棧溢出”。

調(diào)試時,一個簡單的記錄日志的方式是使用 fs.writeFileSync() 以同步的方式寫入日志,這將不會觸發(fā) AsyncHooks 的 init 回調(diào)函數(shù)。

  1. const syncLog = (...args) => fs.writeFileSync('log.txt', `${util.format(...args)}\n`, { flag: 'a' }); 
  2. const hooks = asyncHooks.createHook({ 
  3.   init(asyncId, type, triggerAsyncId, resource) { 
  4.     syncLog('init: ', asyncId, type, triggerAsyncId) 
  5.   } 
  6. }); 
  7. hooks.enable(); 
  8.  
  9. fs.open('hello.txt', (err, res) => { 
  10.   syncLog(`fs.open asyncId: ${asyncId()}, fs.open triggerAsyncId: ${triggerAsyncId()}`); 
  11. }); 

輸出以下內(nèi)容,init 回調(diào)只會被調(diào)用一次,因為 fs.writeFileSync 是同步的是不會觸發(fā) hooks 回調(diào)的。

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

異步之間共享上下文

Node.js v13.10.0 增加了 async_hooks 模塊的 AsyncLocalStorage 類,可用于在一系列異步調(diào)用中共享數(shù)據(jù)。

如下例所示,asyncLocalStorage.run() 函數(shù)第一個參數(shù)是存儲我們在異步調(diào)用中所需要訪問的共享數(shù)據(jù),第二個參數(shù)是一個異步函數(shù),我們在 setTimeout() 的回調(diào)函數(shù)里又調(diào)用了 test2 函數(shù),這一系列的異步操作都不影響我們在需要的地方去獲取 asyncLocalStorage.run() 函數(shù)中存儲的共享數(shù)據(jù)。

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

AsyncLocalStorage 用途很多,例如在服務(wù)端必不可少的日志分析,一個 HTTP 從請求到響應(yīng)整個系統(tǒng)交互的日志輸出如果能通過一個 traceId 來關(guān)聯(lián),在分析日志時也就能夠清晰的看到整個調(diào)用鏈路。

下面是一個 HTTP 請求的簡單示例,模擬了異步處理,并且在日志輸出時去追蹤存儲的 id

  1. const http = require('http'); 
  2. const { AsyncLocalStorage } = require('async_hooks'); 
  3. const asyncLocalStorage = new AsyncLocalStorage(); 
  4. function logWithId(msg) { 
  5.   const id = asyncLocalStorage.getStore(); 
  6.   console.log(`${id !== undefined ? id : '-'}:`, msg); 
  7. let idSeq = 0; 
  8. http.createServer((req, res) => { 
  9.   asyncLocalStorage.run(idSeq++, () => { 
  10.     logWithId('start'); 
  11.     setImmediate(() => { 
  12.       logWithId('processing...'); 
  13.       setTimeout(() => { 
  14.         logWithId('finish'); 
  15.         res.end(); 
  16.       }, 2000) 
  17.     }); 
  18.   }); 
  19. }).listen(8080); 

下面是運(yùn)行結(jié)果,我在第一次調(diào)用之后直接調(diào)用了第二次,可以看到我們存儲的 id 信息與我們的日志一起成功的打印了出來。

image.png

在下一節(jié)會詳細(xì)介紹, 如何在 Node.js 中使用 async hooks 模塊的 AsyncLocalStorage 類處理請求上下文, 也會詳細(xì)講解 AsyncLocalStorage 類是如何實現(xiàn)的本地存儲。

Reference

https://nodejs.org/dist/latest-v14.x/docs/api/async_hooks.html

 

責(zé)任編輯:武曉燕 來源: Nodejs技術(shù)棧
相關(guān)推薦

2021-04-06 10:15:29

Node.jsHooks前端

2021-08-12 01:00:29

NodejsAsync

2021-01-27 08:05:55

本地存儲HTTP

2023-07-14 22:36:42

Node.jsStorage

2021-09-26 05:06:04

Node.js模塊機(jī)制

2011-12-23 13:58:57

node.js

2025-01-13 00:00:00

2021-01-18 08:06:38

Node.js 追蹤JSON

2020-12-08 06:28:47

Node.js異步迭代器

2020-04-15 15:48:03

Node.jsstream前端

2021-03-04 23:12:57

Node.js異步迭代器開發(fā)

2019-12-17 11:40:44

Node.js模塊前端

2022-03-20 06:40:31

Node.jsperf_hooks性能數(shù)據(jù)

2014-02-19 16:28:53

Node.jsWeb工具

2023-06-30 23:25:46

HTTP模塊內(nèi)存

2021-03-16 16:16:41

GeneratorWebsockets前端

2022-08-28 16:30:34

Node.jsDocker指令

2011-12-09 11:16:48

Node.js

2023-01-10 14:11:26

2021-03-01 08:03:26

Node.jsStream模塊
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號