Async Hooks are a new Node8 feature that provides apis for tracking the life cycle of asynchronous resources in NodeJs. They are part of the NodeJs built-in module and can be directly referenced.
const async_hooks = require('async_hooks');
Copy the code
This is a module that is rarely used, so why is there this module?
As we all know, JavaScript was designed as a single-threaded language at the very beginning, which is related to its original design. JavaScript was originally used only for form verification of pages, reducing the time cost of waiting for server response in the era of low network speed. With the development of Web front-end technology, although the front-end function is more and more powerful, more and more attention, but the single thread does not seem to have what can not solve the problem, compared to multithreading seems to be more complex, so the single thread is still used so far.
Since JavaScript is single-threaded, there are always some time-consuming tasks in daily development, such as timers, and Ajax, which has been standardized now. In order to solve these problems, JavaScript has divided itself into BOM, DOM, and ECMAScript. BOM helps us solve these time-consuming tasks, called asynchronous tasks.
Because the browser BOM helps us handle asynchronous tasks, most programmers know little about asynchronous tasks other than how to use them, such as how many asynchronous tasks are in the queue at the same time? NodeJS provides an Experimental API that allows us to use async_hooks if we want to use it in some cases. NodeJS provides an Experimental API that allows us to use async_hooks if we want to use it in some cases. Why NodeJS? Because only asynchronous modules such as timers and HTTP can be controlled by the developer in Node, the BOM in the browser is not controlled by the developer unless the browser provides the corresponding API.
Async_hooks rules
Each function will provide a context, which is called async scope. Each async scope has an asyncId, which is the mark of the current async scope. The asyncId in the same async scope must be the same.
This allows us to distinguish which asynchronous task to listen on when multiple asynchronous tasks are running in parallel.
AsyncId is an incremented, non-repeating positive integer. The first asyncId in a program must be 1.
In general terms, async scope is a synchronous task that cannot be interrupted. As long as it cannot be interrupted, no matter how long the code is, it will share an asyncId. However, if it can be interrupted in the middle, such as callback or await in the middle, a new asynchronous context will be created and a new asyncId will be created.
Each async scope has a triggerAsyncId that indicates that the current function is generated by that async scope;
AsyncId and triggerAsyncId make it very easy to trace the entire asynchronous call relationship and link.
Async_links.executionasyncid () is used to get asyncId, and you can see that the global asyncId is 1.
Async_links.triggerasyncid () is used to get triggerAsyncId, currently 0.
const async_hooks = require('async_hooks');
console.log('asyncId:', async_hooks.executionAsyncId()); // asyncId: 1
console.log('triggerAsyncId:', async_hooks.triggerAsyncId()); // triggerAsyncId: 0
Copy the code
Fs. open asyncId is 7 and fs.open triggerAsyncId is 1. This is because fs.open is triggered by a global call and global asyncId is 1.
const async_hooks = require('async_hooks');
console.log('asyncId:', async_hooks.executionAsyncId()); // asyncId: 1
console.log('triggerAsyncId:', async_hooks.triggerAsyncId()); // triggerAsyncId: 0
const fs = require('fs');
fs.open('./test.js'.'r'.(err, fd) = > {
console.log('fs.open.asyncId:', async_hooks.executionAsyncId()); / / 7
console.log('fs.open.triggerAsyncId:', async_hooks.triggerAsyncId()); / / 1
});
Copy the code
The life cycle of asynchronous functions
The correct use of async_hooks is to trigger callbacks before all asynchronous tasks are created, executed, executed, and destroyed. All callbacks pass in asyncId.
We can use async_links.createHook to create a hook for an asynchronous resource that takes an object as an argument to register callback functions for events that may occur during the lifetime of the asynchronous resource. These hook functions are triggered whenever an asynchronous resource is created/executed/destroyed.
const async_hooks = require('async_hooks');
const asyncHook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId, resource){},destroy(asyncId){}})Copy the code
The createHook function can accept five types of Hook Callbacks:
-
1.init(asyncId, type, triggerAsyncId, resource)
-
The init callback is usually triggered when an asynchronous resource is initialized.
-
AsyncId: Each asynchronous resource generates a unique flag
-
Type: indicates the type of the asynchronous resource, usually the name of the resource’s constructor.
FSEVENTWRAP, FSREQCALLBACK, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPINCOMINGMESSAGE,
HTTPCLIENTREQUEST, JSSTREAM, PIPECONNECTWRAP, PIPEWRAP, PROCESSWRAP, QUERYWRAP,
SHUTDOWNWRAP, SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPSERVERWRAP, TCPWRAP,
TTYWRAP, UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST,
RANDOMBYTESREQUEST, TLSWRAP, Microtask, Timeout, Immediate, TickObject
Copy the code
-
TriggerAsyncId: Represents the asyncId of the corresponding async scope that triggers the creation of the current asynchronous resource
-
Resource: represents the initialized asynchronous resource object
We can async_hooks. CreateHook function to register for each asynchronous resource in the life cycle of the init/before/after/destory/promiseResolve related event monitoring function; The same async scope may be called and executed for many times. No matter how many times it is executed, its asyncId must be the same. By monitoring functions, it is convenient to track The Times and time of execution and the relationship between the lines.
- 2.before(asyncId)
Before is usually called before the callback is executed after the asyncId operation is completed. Before may be executed several times, depending on the number of times it is called back. Caution should be paid when using before.
- 3.after(asyncId)
The after callback is usually called immediately after the callback is executed for the asynchronous resource. If an uncaughtException occurs during the execution of the callback, the after event is called after the “uncaughtException” event is emitted.
- 4.destroy(asyncId)
AsyncId is called when the corresponding asynchronous resource is destroyed. The destruction of some asynchronous resources depends on garbage collection, so in some cases the DeStory event may never be emitted due to a memory leak.
- 5.promiseResolve(asyncId)
When the REsovle function in the Promise constructor is executed, the promiseResolve event is emitted. In some cases, resolve is implicitly executed, such as when the. Then function returns a new Promise, which is also called.
const async_hooks = require('async_hooks');
// Gets the asyncId of the current execution context
const eid = async_hooks.executionAsyncId();
// Gets the asyncId that triggers the current function
const tid = async_hooks.triggerAsyncId();
// Create a new AsyncHook instance. All of these callbacks are optional
const asyncHook =
async_hooks.createHook({ init, before, after, destroy, promiseResolve });
// The declaration must be displayed to execute
asyncHook.enable();
// Disallows listening for new asynchronous events.
asyncHook.disable();
function init(asyncId, type, triggerAsyncId, resource) {}function before(asyncId) {}function after(asyncId) {}function destroy(asyncId) {}function promiseResolve(asyncId) {}Copy the code
Promise
Promise is a special case, and if you’re careful enough in the type of the init method you’ll see that there’s no promise init. If you use ah.executionAsyncid () only to get the Promise’s asyncId, you will not get the correct ID. Only after adding the actual hook will asynC_hooks create asyncId for the Promise’s callback.
In other words, we don’t assign new asyncId to promises by default due to V8’s high execution cost for fetching asyncId.
By default, promises or async/await will not get the correct asyncId and triggerId in the current context. However, we can force the allocation of asyncId to promises by executing the async_links.createhook (callbacks).enable() function.
const async_hooks = require('async_hooks');
const asyncHook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId, resource){},destroy(asyncId) { }
})
asyncHook.enable();
Copy the code
Promise.resolve(123).then(() = > {
console.log(`asyncId ${async_hooks.executionAsyncId()} triggerId ${async_hooks.triggerAsyncId()}`);
});
Copy the code
Promises trigger only init and promiseResolve hook functions, whereas before and after hook functions are triggered only when a Promise is called in a chain, i.e. only when a Promise is generated in a. Then /. Catch function.
new Promise(resolve= > {
resolve(123);
}).then(data= > {
console.log(data);
})
Copy the code
As you can see, there are two promises, the first created by new instantiation and the second created by THEN (see the previous Promise source article if you don’t understand).
The sequence is that the init function itself is called when the new Promise is executed, and then the promiseResolve function is called when the resolve function is executed. The second Promise’s init function is then executed in the then method, followed by the second Promise’s before, promiseResovle, after function.
Exception handling
If an exception occurs in the registered async-hook callback function, the service prints an error log and exits immediately, all DE listeners are removed, and an ‘exit’ event is emitted to exit the program.
The reason for exiting the process immediately is that if these async-hook functions run stably, the next same event is likely to throw an exception. The main purpose of these functions is to listen for asynchronous events, if unstable should be detected and corrected in time.
Print logs in asynchronous hook callbacks
Since console.log is also an asynchronous call, if we call console.log in an async-hook function, the corresponding hook event will be triggered again, resulting in an infinite loop. So we must use synchronous printing log to trace in async-hook function, we can use fs.writesync function:
const fs = require('fs');
const util = require('util');
function debug(. args) {
fs.writeFileSync('log.out'.`${util.format(... args)}\n`, { flag: 'a' });
}
Copy the code
AsyncHooks] (nodejs.org/dist/latest…)