Lang
Blog

Async Hooks in Node.js- Features & Use Cases

ByShankar Morwal
September 9th . 5 min read
Async Hooks in Node.js- Features & Use Cases

Node.js 8 version released a new module called async_hooks. It provides an easy-to-use API to track the lifetime of async resources in Node.js applications.

It was introduced by the Node team in 2017 and is still in the experimental state.

The asynchronous resources mentioned here are the objects created by Node.JS that have associated callback and can be called multiple times. Some examples of asynchronous resources are Timeouts, Promises, TCPWRAP, Immediates, etc.

We’ll learn more about Async hooks and their use cases in this blog. Let’s get going then-

Why Do We Need Async Hooks?

Async resources are created by async process for example file read, database read operation, external API call. So naturally async resources keep track of callback which will be called once the process is completed. But, if we need to track the async resource like what is happening in the middle of an async resource execution, we don’t have any way to do that.

The typical lifecycle of such an async resource is similar to this:

async-hooks-in-node-js-features-use-cases – 1.jpg

To solve this concern, Nodejs provided async hooks to spy on the lifecycle of async resources.

Enough talking, show me the code!

Hello word to async hooks-

const fs = require('fs');
const { fd } = process.stdout;
const async_hooks = require('async_hooks')
function init(asyncId, type, triggerAsyncId, resource) {
    fs.writeSync(fd, `Init callback async id --> ${asyncId} \n`);
}
function destroy(asyncId) {
    fs.writeSync(fd, `Destroy callback async id -->${asyncId} \n`);
}
const asyncHook = async_hooks.createHook({ init: init, destroy: destroy });
asyncHook.enable();

setTimeout(()=>{
    //do nothing
}, 1000);
//output
/*
Init callback async id --> 4
Destroy callback async id -->4
*/

In the example above, we have setTimeout is making an async operation. We have created two async hooks init and destroy. When we execute the code, we can see that it is calling init first, and then callsdestroy function. You might be wondering why we are using fs.writeSync to print on console rather than using console.log.

Console.log is async function itself, so this will cause async hooks again, and will cause infinite recursion. So we can not use any async code inside async hooks. It will be discussed later on again in later in this article.

Async Hooks APIs

Essentially, the async hooks API provides these five key event functions that are called during different time and instances of the resource’s lifecycle, to track the async resources.

async-hooks-in-node-js-features-use-cases – 2.jpg

You have to specify the event that you want to trigger out of the following while creating an instance.

All the callbacks are optional. Let’s make this statement a little more obvious- It means if the cleanup data of the resource is to be tracked, then you only need to pass the destroy callback.

init

The init callback is called whenever a call is created that has the possibility of triggering an asynchronous event. Just for the record, at this point, we’ve already associated the hook with an async resource.

init callback receives these parameters when called-

  • asyncId: Each async resource, when identified, is allotted with a unique ID.
  • type: It depicts the type of the async resource in string form that triggered init callback.
  • triggerAsyncId: asyncId of the resource for whose context the async resource was created. It shows why a specific resource was created.
  • resource: It represents the reference to the async operation that is released during destroy.

All the other callbacks get only one parameter- asyncId.

Before

Whenever an async operation initiates or completes, a callback notifies the user of its status. The before callback is called right before this mentioned callback is executed, and the relevant resource will be assigned with a unique asyncId identifier.

The before callback can be called any times from 0 to n. For instance, when the asynchronous operation gets cancelled, it will be called 0 times, on the other hand, the persistent type resources can call it multiple times.

After

Similar to before, it is called post-execution of the relevant asynchronous resource or right after when the specified callback in before callback finishes its execution.

Destroy

It’s quite easy to guess, isn’t it? Yes, you guessed it right!

It is called every time the asynchronous resource, corresponding to the unique asyncId, is destroyed regardless of whatever happened to its callback function.

However, in some cases, the resource depend upon the garbage collection for its cleanup which might cause a memory leak in the application, which results in avoid calling destroy. If the resource does not rely upon the garbage, then it won’t be a problem and destroy will do the cleaning.

promiseResolve

This callback is triggered when the Promise gets its resolve function, which is invoked either directly or by some external means to resolve a promise.

async-hooks-in-node-js-features-use-cases – 3.jpg

Use Cases of Async Hooks

Listed below are some major features and use cases of async hooks-

- Promise Execution Tracking

Async hooks are preferably used to track the lifecycle of promises and their execution, as promises are also asynchronous resources.

Whenever a new promise is created, the init callback runs. The before and after hooks run pre- and post-completion of a PromiseReactionJob and the resolve hook is called when a promise is resolved.

- Web Request Context Handling

This is another major use case of Async Hooks, where it stores relevant information in context to the request during its lifetime. This feature becomes highly useful to track the user’s behavior in a server.

With async hooks, you can easily store the context data and access it anywhere and anytime in the code.

The process starts with a new request to the server, which initiates calling createRequestContext function to get data to store in a Map. Every async operation initiated as part of this request will be saved in the Map along with the context data (Here init plays an important part).

Next, destroy keeps the size of the Map under control and do the cleaning. This is how you can get the current execution context by calling getContextData anytime.

- Error Handling with Async Hooks

Whenever an async hook callback throws an error, the application follows the stack trace and exits due to an uncaught exception. Unless you run the application with-abort-on-uncaught-exception, which will print the stack trace exiting the application, the exit callbacks will still be called.

This error handling behavior is due to the fact that all these callbacks run at potentially unstable points, for instance during construction or destruction of a class. This process prevents any unintentional and potential abort to the process by closing it quickly.

- Printing in Async Hooks

Printing is an asynchronous operation and console.log() triggers calling async callbacks. However, using such asynchronous operations inside async callbacks causes infinite recursion.

For instance, when init runs, the console.log() will trigger another init callback causing endless recursion and increasing stack size.

To avoid this, you should use a synchronous operation like fs.writeFileSync(file, msg, flag) while debugging as it will print to the file without invoking AsyncHooks recursively.

If the logging requires an asynchronous operation, you can track what caused the async operation to initiate using AsyncHooks. As it was logging itself that invoked the AsyncHooks callback, you can skip it which will break the infinite recursion cycle.

- Enhanced Stack Traces

The creator of Node.js, Ryan Dahl, talked about debugging problems in node.js due to event loops, which kills the stack trace. As the async hooks facilitate better tracing of async resources, it allows developers to improve and enrich the stack trace.

Bottom Line

Do you know that you can also measure the duration of an asynchronous operation in real-time? Yes you can, but by integrating Async hooks module with Performance API module.

Well, this was all about async hooks. I would suggest you to refer to the link if you are interested to learn more about async hooks-ink-

If you liked this blog, don’t forget to hit the clap icon (multiple times 😉).

Share:
0
+0