Node.js Async Hooks: The power of asynchronous programming

The Node.js async hooks module has ever caught your attention, right? I would urge you to familiarize yourself with it. Even though this module is in experimental mode (released with Node.js 9) and isn’t recommended for production use, you should learn more about it.

With Node.js async hooks, in particular the async_hooks module, async resources are tracked in a clear and easy-to-use fashion.

Simply type JavaScript require import to access the API in its simplest form:

const async_hooks = require(‘async_hooks’);

But… what exactly do you mean by asynchronous resources? Nowadays, many things in the JavaScript universe take place asynchronously. This is especially true for Node.js.

Node.js creates (or allows us to create) objects that have a callback associated with them, regardless of how many times the callback is invoked. In this context, asynchronous means that asynchronous isn’t taking place at the same time. There are several types out there: Promises, servers, timeouts, etc.

Almost all languages can have limited resources. When this happens, you also need to remember to close them. Containers and languages close some of them. Consequently, you might not even get a callback at all. There’s nothing wrong with that. There is no discrimination between these scenarios when using AsyncHook.

This article was written to explore hooks a bit deeper and try to give you my overview on the subject, with some examples here and there to help digest the whole thing. Ready?

Why Do We Need Async Hooks?

Asynchronous processes create async resources, such as file reads, database reads, and external API calls. Consequently, async resources track callbacks after the process is complete. Nevertheless, if we need to track an async resource, such as what’s occurring in the middle of it, we are unable to do this. 

Use Cases of Async Hooks

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

1. Promise Execution Tracking

In addition to being asynchronous resources, promises also participate in the lifecycle of asynchronous hooks.

This callback is triggered whenever a new promise is created. Pre- and post-completion hooks are run before and after a PromiseReactionJob is completed. The resolve hook runs when a promise is resolved.

2. Web Request Context Handling

Async Hooks can also be used to store relevant information about request overtime. Tracking the behavior of a user on a server is highly useful.

Async hooks allow you to store context data in a single place and access it from anywhere in the code.

In order to get data to store in a Map, a new request is sent to the server, which calls the createRequestContext function. The map will contain the context data, as well as the async operations initiated during the current request (init is important here). 

In addition to this, clean and destroy the Map to keep it under control. The current execution context can be obtained at any time by calling getContextData.

3. Error Handling with Async Hooks

The application exits if an async hook callback throws an uncaught exception and follows the stack trace. Even when this option is turned off, the exit callbacks will still be called if the application is aborted due to an uncaught exception.

Because the callbacks all run at potentially unstable points like, during construction or destruction of a class, they display the error handling behavior. Closing it quickly prevents any unintentional or potential breaks in the process.

4. Printing in Async Hooks

The console.log() function triggers asynchronous callbacks when printing happens. These asynchronous operations, however, result in infinite recursion in async callbacks. 

This causes endless recursion by triggering another init callback when init runs, for instance.

When debugging, try not to invoke AsyncHooks recursively by using asynchronous operations like fs.writeFileSync(file, message, flag) as this will prevent this from happening.

AsyncHooks can tell you what caused the asynchronous operation to begin if the logging involves an asynchronous operation. Since the AsyncHooks callback was invoked by the logs themselves, you can skip it, thus breaking the infinite recursion cycle.

5. Enhanced Stack Traces

Ryan Dahl, the creator of Node.js, talked about debugging difficulties in node.js due to event loops, which destroy the stack trace. Stack traces can be improved and enriched through async hooks, which permit better tracing of async resources.