JavaScript >> Javascript 文檔 >  >> Node.js

在 Node.js 中使用異步鉤子進行請求上下文處理

簡介

Async Hooks 是 Node.js 中的一個核心模塊,它提供了一個 API 來跟踪 Node 應用程序中異步資源的生命週期。可以將異步資源視為具有關聯回調的對象。

示例包括但不限於:Promises、Timeouts、TCPWrap、UDP 等。我們可以使用此 API 跟踪的異步資源的完整列表可以在這裡找到。

Async Hooks 功能於 2017 年在 Node.js 版本 8 中引入,目前仍處於試驗階段。這意味著仍可能對 API 的未來版本進行向後不兼容的更改。話雖如此,它目前被認為不適合生產。

在本文中,我們將深入了解 Async Hooks - 它們是什麼,為什麼它們很重要,我們可以在哪裡使用它們以及如何將它們用於特定用例,即在 Node.js 中處理請求上下文。 js和Express應用程序。

什麼是異步掛鉤?

如前所述,Async Hooks 類是一個核心 Node.js 模塊,它提供了一個 API,用於跟踪 Node.js 應用程序中的異步資源。這還包括跟踪由原生 Node 模塊創建的資源,例如 fsnet .

在異步資源的生命週期中,有 4 個事件會觸發,我們可以使用 Async Hooks 進行跟踪。其中包括:

  1. init - 在構建異步資源期間調用
  2. before - 在調用資源的回調之前調用
  3. after - 在調用資源的回調後調用
  4. destroy - 在異步資源銷毀後調用
  5. promiseResolve - resolve() 時調用 調用 Promise 的函數。

以下是 Node.js 文檔概述中 Async Hooks API 的摘要片段:

const async_hooks = require('async_hooks');

const exec_id = async_hooks.executionAsyncId();
const trigger_id = async_hooks.triggerAsyncId();
const asyncHook = async_hooks.createHook({
  init: function (asyncId, type, triggerAsyncId, resource) { },
  before: function (asyncId) { },
  after: function (asyncId) { },
  destroy: function (asyncId) { },
  promiseResolve: function (asyncId) { }
});
asyncHook.enable();
asyncHook.disable();

executionAsyncId() 方法返回當前執行上下文的標識符。

triggerAsyncId() 方法返回觸發異步資源執行的父資源的標識符。

createHook() 方法創建一個異步鉤子實例,將上述事件作為可選回調。

為了能夠跟踪我們的資源,我們調用 enable() 我們使用 createHook() 創建的異步鉤子實例的方法 方法。

我們還可以通過調用 disable() 來禁用跟踪 功能。

了解了 Async Hooks API 的含義後,讓我們看看為什麼要使用它。

何時使用異步 Hooks

將 Async Hooks 添加到核心 API 具有許多優勢和用例。其中包括:

  1. 更好的調試 - 通過使用 Async Hooks,我們可以改進和豐富異步函數的堆棧跟踪。
  2. 強大的跟踪功能,尤其是與 Node 的 Performance API 結合使用時。此外,由於 Async Hooks API 是原生的,因此性能開銷最小。
  3. Web 請求上下文處理 - 在請求的生命週期內捕獲請求的信息,而無需在任何地方傳遞請求對象。使用 Async Hooks 這可以在代碼中的任何位置完成,並且在跟踪服務器中用戶的行為時特別有用。

在本文中,我們將了解如何在 Express 應用程序中使用 Async Hooks 處理請求 ID 跟踪。

使用異步掛鉤處理請求上下文

在本節中,我們將說明如何利用 Async Hooks 在 Node.js 應用程序中執行簡單的請求 ID 跟踪。

設置請求上下文處理程序

我們將首先創建一個目錄來存放我們的應用程序文件,然後進入該目錄:

mkdir async_hooks && cd async_hooks 

接下來,我們需要在這個目錄中使用 npm 初始化我們的 Node.js 應用程序 和默認設置:

npm init -y

這將創建一個 package.json 目錄根目錄下的文件。

接下來,我們需要安裝 Expressuuid 包作為依賴項。我們將使用 uuid 包為每個傳入的請求生成一個唯一的 ID。

最後,我們安裝esm 模塊,因此低於 v14 的 Node.js 版本可以運行此示例:

npm install express uuid esm --save

接下來,創建一個 hooks.js 目錄根目錄下的文件:

touch hooks.js

該文件將包含與 async_hooks 交互的代碼 模塊。它導出兩個函數:

  • 為 HTTP 請求啟用異步掛鉤,跟踪其給定的請求 ID 和我們想要保留的任何請求數據。
  • 另一個返回由鉤子管理的請求數據,給定其異步鉤子 ID。

讓我們把它寫成代碼:

require = require('esm')(module);
const asyncHooks = require('async_hooks');
const { v4 } = require('uuid');
const store = new Map();

const asyncHook = asyncHooks.createHook({
    init: (asyncId, _, triggerAsyncId) => {
        if (store.has(triggerAsyncId)) {
            store.set(asyncId, store.get(triggerAsyncId))
        }
    },
    destroy: (asyncId) => {
        if (store.has(asyncId)) {
            store.delete(asyncId);
        }
    }
});

asyncHook.enable();

const createRequestContext = (data, requestId = v4()) => {
    const requestInfo = { requestId, data };
    store.set(asyncHooks.executionAsyncId(), requestInfo);
    return requestInfo;
};

const getRequestContext = () => {
    return store.get(asyncHooks.executionAsyncId());
};

module.exports = { createRequestContext, getRequestContext };

在這段代碼中,我們首先需要 esm 模塊為不支持實驗模塊導出的 Node 版本提供向後兼容性。 uuid 內部使用此功能 模塊。

接下來,我們還需要 async_hooksuuid 模塊。從 uuid 模塊,我們解構 v4 函數,我們稍後將使用它來生成版本 4 UUID。

接下來,我們創建一個將每個異步資源映射到其請求上下文的存儲。為此,我們使用了一個簡單的 JavaScript 映射。

接下來,我們調用 createHook() async_hooks 的方法 模塊並實現 init()destroy() 回調。在實現我們的init() 回調,我們檢查 triggerAsyncId 商店裡有。

免費電子書:Git Essentials

查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!

如果存在,我們創建 asyncId 的映射 到triggerAsyncId下存儲的請求數據 .這實際上確保我們為子異步資源存儲相同的請求對象。

destroy() 回調檢查商店是否有 asyncId 資源,如果為真則將其刪除。

要使用我們的鉤子,我們通過調用 enable() 來啟用它 asyncHook 的方法 我們創建的實例。

接下來,我們創建 2 個函數 - createRequestContext()getRequestContext 我們用來分別創建和獲取我們的請求上下文。

createRequestContext() 函數接收請求數據和唯一 ID 作為參數。然後它創建一個 requestInfo 來自兩個參數的對象,並嘗試使用當前執行上下文的異步 ID 作為鍵和 requestInfo 更新存儲 作為值。

getRequestContext() 另一方面,函數檢查存儲是否包含與當前執行上下文的 ID 對應的 ID。

我們最終使用 module.exports() 導出這兩個函數 語法。

我們已經成功地設置了我們的請求上下文處理功能。讓我們繼續設置我們的 Express 將接收請求的服務器。

設置 Express 服務器

設置好上下文後,我們現在將繼續創建我們的 Express 服務器,以便我們可以捕獲 HTTP 請求。為此,請創建一個 server.js 目錄根目錄下的文件如下:

touch server.js

我們的服務器將接受端口 3000 上的 HTTP 請求。它通過調用 createRequestContext() 創建一個 Async Hook 來跟踪每個請求 在中間件中 function - 一個可以訪問 HTTP 的請求和響應對象的函數。然後,服務器發送一個 JSON 響應,其中包含 Async Hook 捕獲的數據。

server.js 內部 文件,輸入以下代碼:

const express = require('express');
const ah = require('./hooks');
const app = express();
const port = 3000;

app.use((request, response, next) => {
    const data = { headers: request.headers };
    ah.createRequestContext(data);
    next();
});

const requestHandler = (request, response, next) => {
    const reqContext = ah.getRequestContext();
    response.json(reqContext);
    next()
};

app.get('/', requestHandler)

app.listen(port, (err) => {
    if (err) {
        return console.error(err);
    }
    console.log(`server is listening on ${port}`);
});

在這段代碼中,我們需要 express 和我們的 hooks 模塊作為依賴項。然後我們創建一個 Express 應用程序通過調用 express() 功能。

接下來,我們設置一個中間件,將請求標頭解構,並將它們保存到一個名為 data 的變量中 .然後它調用 createRequestContext() 函數傳遞 data 作為論據。這確保了請求的標頭將在請求的整個生命週期中通過異步鉤子保留。

最後,我們調用 next() 函數轉到我們中間件管道中的下一個中間件或調用下一個路由處理程序。

在我們的中間件之後,我們編寫 requestHandler() 處理 GET 的函數 服務器根域上的請求。你會注意到在這個函數中,我們可以通過 getRequestContext() 訪問我們的請求上下文 功能。此函數返回一個對象,該對象表示在請求上下文中生成並存儲的請求標頭和請求 ID。

然後我們創建一個簡單的端點並將我們的請求處理程序作為回調附加。

最後,我們通過調用 listen() 讓我們的服務器監聽 3000 端口上的連接 我們應用實例的方法。

在運行代碼之前,打開 package.json 目錄根目錄下的文件並替換 test 腳本的一部分:

"start": "node server.js"

完成後,我們可以使用以下命令運行我們的應用程序:

npm start

您應該會在終端上收到一條響應,表明該應用程序正在端口 3000 上運行,如下所示:

> [email protected] start /Users/allanmogusu/StackAbuse/async-hooks-demo
> node server.js

(node:88410) ExperimentalWarning: Conditional exports is an experimental feature. This feature could change at any time
server is listening on 3000

隨著我們的應用程序運行,打開一個單獨的終端實例並運行以下 curl 命令來測試我們的默認路由:

curl http://localhost:3000

這個curl 命令生成 GET 請求我們的默認路由。你應該得到類似這樣的回應:

$ curl http://localhost:3000
{"requestId":"3aad88a6-07bb-41e0-ab5a-fa9d5c0269a7","data":{"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"}}}%

注意生成的requestId 並返回我們的請求標頭。重複該命令應該會生成一個新的請求 ID,因為我們將發出一個新請求:

$ curl http://localhost:3000
{"requestId":"38da84792-e782-47dc-92b4-691f4285b172","data":{"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"}}}%

響應包含我們為請求生成的 ID 和我們在中間件函數中捕獲的標頭。使用 Async Hooks,我們可以輕鬆地將數據從一個中間件傳遞到另一個中間件以處理相同的請求。

結論

Async Hooks 提供了一個 API,用於跟踪 Node.js 應用程序中異步資源的生命週期。

在本文中,我們簡要介紹了 Async Hooks API、它提供的功能以及我們如何利用它。我們專門介紹了一個基本示例,說明如何使用 Async Hooks 高效、乾淨地進行 Web 請求上下文處理和跟踪。

然而,從 Node.js 版本 14 開始,Async Hooks API 附帶異步本地存儲,這是一個使 Node.js 中的請求上下文處理更容易的 API。你可以在這裡讀更多關於它的內容。另外,本教程的代碼可以在這裡訪問。


Tutorial JavaScript 教程
  1. Javascript:轉發接受可變數量參數的函數調用

  2. 按日期對 desc 進行排序,如果並列則按 javascript 數組中的風險排序

  3. 為什麼onclick函數會出錯?

  4. 介紹 LambdaStarter.js

  5. 使用畫布和 requestAnimationFrame 構建蛇遊戲

  6. 不要在代碼中留下 TODO!

  7. 想要轉向全棧開發但不確定從哪裡開始?

  1. 在 React 中構建自定義鉤子以獲取數據

  2. #100daysofcode 第 19 天:昨天的工作

  3. 從頭開始創建 Netflix 克隆:JavaScript PHP + MySQL 第 47 天

  4. Angular 14 日曆與 ngx-bootstrap 日期選擇器教程

  5. React Native 生命週期方法與 Hooks 指南

  6. 2022 年要學習的 5 種編程語言

  7. 第 9 天 - 反應中的 ref 是什麼?

  1. 將表單數據轉換為 JavaScript 對象

  2. 代碼簡介:隊列數據結構的工作原理

  3. 如何在 JavaScript 中反轉數組

  4. 挑戰:構建一個 React 組件