Using Async Hooks for Request Context Handling in Node.js
介绍
异步挂钩是Node.js中的核心模块,它提供API来跟踪Node应用程序中异步资源的生存期。 异步资源可以被认为是具有关联回调的对象。
示例包括但不限于:Promises,Timeouts,TCPWrap,UDP等。我们可以在此处使用此API跟踪的异步资源的完整列表。
Async Hooks功能于2017年在Node.js版本8中引入,目前仍处于试验阶段。 这意味着仍可能对API的将来版本进行向后不兼容的更改。 话虽如此,它目前不适合生产。
在本文中,我们将深入研究异步挂钩-它们是什么,为什么重要,我们可以在哪里使用它们,以及如何在特定用例(即在Node中请求上下文处理)中利用它们。 js和Express应用程序。
什么是异步挂钩?
如前所述,Async Hooks类是一个核心的Node.js模块,它提供用于跟踪Node.js应用程序中的异步资源的API。 这还包括跟踪由本机节点模块(例如
在异步资源的生存期内,有4个事件会触发,我们可以使用Async Hooks进行跟踪。 这些包括:
以下是Node.js文档中概述的Async Hooks API的摘要:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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(); |
为了跟踪资源,我们调用了异步钩子实例的
我们还可以通过调用
在了解了Async Hooks API的含义之后,我们来看看为什么要使用它。
何时使用异步挂钩
将Async Hooks添加到核心API已经获得了许多优势和用例。 其中一些包括:
更好的调试-通过使用异步挂钩,我们可以改善和丰富异步函数的堆栈跟踪。
强大的跟踪功能,尤其是与Node的Performance API结合使用时。 另外,由于Async Hooks API是本机的,因此性能开销最小。
Web请求上下文处理-在该请求的生存期内捕获请求的信息,而无需将请求对象传递到任何地方。 使用异步挂钩可以在代码中的任何地方完成,在跟踪服务器中用户的行为时特别有用。
在本文中,我们将研究如何在Express应用程序中使用Async Hooks处理请求ID跟踪。
使用异步挂钩进行请求上下文处理
在本节中,我们将说明如何利用异步挂钩在Node.js应用程序中执行简单的请求ID跟踪。
设置请求上下文处理程序
我们将首先创建一个目录,应用程序文件将驻留在该目录中,然后移入该目录:
1 | mkdir async_hooks && cd async_hooks |
接下来,我们需要使用
1 | npm init -y |
这将在目录的根目录下创建一个
接下来,我们需要安装
最后,我们安装
1 | npm install express uuid esm --save |
接下来,在目录的根目录下创建一个
1 | touch hooks.js |
该文件将包含与
一个为HTTP请求启用Async Hook,跟踪其给定的请求ID和我们希望保留的任何请求数据的跟踪器。
另一个在给定其异步挂钩ID的情况下返回由挂钩管理的请求数据。
让我们将其放入代码中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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 }; |
在这段代码中,我们首先需要
接下来,我们还需要
接下来,我们创建一个存储,它将每个异步资源映射到其请求上下文。 为此,我们利用了一个简单的JavaScript映射。
接下来,我们调用
如果存在,我们将创建
要使用挂钩,我们可以通过调用已创建的
接下来,我们创建2个函数-
另一方面,
最后,我们使用
我们已经成功设置了请求上下文处理功能。 让我们继续设置将接收请求的
设置Express服务器
设置完上下文后,我们现在将继续创建
1 | touch server.js |
我们的服务器将在端口3000上接受HTTP请求。它通过在中间件函数(可访问HTTP请求和响应对象的函数)中调用
在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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}`); }); |
在这段代码中,我们需要
接下来,我们设置一个中间件,该中间件对请求标头进行解构,将其保存到名为
最后,我们调用
在我们的中间件之后,我们编写了
然后,我们创建一个简单的端点并将请求处理程序附加为回调。
最后,通过调用应用实例的
在运行代码之前,打开目录根目录下的
1 | "start":"node server.js" |
完成后,我们可以使用以下命令运行我们的应用程序:
1 | npm start |
您应该在终端上收到响应,指示该应用程序正在端口3000上运行,如下所示:
1 2 3 4 5 | > <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="3554464c5b56185d5a5a5e46185150585a75041b051b05">[emailprotected]</a> 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 |
在我们的应用程序运行的情况下,打开一个单独的终端实例并运行以下
1 | curl http://localhost:3000 |
此
1 2 | $ curl http://localhost:3000 {"requestId":"3aad88a6-07bb-41e0-ab5a-fa9d5c0269a7","data":{"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"}}}% |
请注意,将返回生成的
1 2 | $ 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,我们可以轻松地将数据从一个中间件传递到另一个中间件,以进行相同的请求。
结论
异步挂钩提供了一个API,用于跟踪Node.js应用程序中异步资源的生存期。
在本文中,我们简要介绍了Async Hooks API,其提供的功能以及如何利用它。 我们专门介绍了一个基本示例,说明如何使用异步挂钩来有效,干净地进行Web请求上下文处理和跟踪。
但是从Node.js版本14开始,Async Hooks API附带了异步本地存储,该API使Node.js中的请求上下文处理更加容易。 你可以在这里读更多关于它的内容。 另外,可以在此处访问本教程的代码。