DEV Community

loading...

Amazing feature landed in Node.js - Breaking free from the request and avoid argument drilling with AsyncHooks

Michael Z
Software writer
Originally published at michaelzanggl.com Updated on ・2 min read

I didn't pay too much attention to the features landed in Node 13, something I will definitely do from now as they have introduced an absolutely amazing new feature.

From frameworks in other languages like Laravel/PHP you might be used to getting the authenticated user like this:

use Illuminate\Support\Facades\Auth;

// Get the currently authenticated user...
$user = Auth::user();
Enter fullscreen mode Exit fullscreen mode

You can only get the authenticated user through the request as this is where you have access to cookies, headers, etc. Yet, the above code example doesn't mention anything about the request. Laravel provides us this expressive facade and does the dirty work under the hood, where it still accesses the request.

This is possible in PHP because each request is completely isolated from one another.

In Node.js, for a long time, you needed to get the authenticated user from the request and then pass down to every function that needs it.

class PostController {
  async index({ auth }) {
    await new PostService().fetch(auth.user)
  }
}
Enter fullscreen mode Exit fullscreen mode

Until now.

Node 13 comes with a new feature called AsyncLocalStorage.

From the documentation it says: It allows storing data throughout the lifetime of a web request or any other asynchronous duration.

And here is an example:

const { AsyncLocalStorage } = require('async_hooks');
const http = require('http');

const requestKey = 'CURRENT_REQUEST';
const asyncLocalStorage = new AsyncLocalStorage();

function log(...args) {
  const store = asyncLocalStorage.getStore();
  // Make sure the store exists and it contains a request.
  if (store && store.has(requestKey)) {
    const req = store.get(requestKey);
    // Prints `GET /items ERR could not do something
    console.log(req.method, req.url, ...args);
  } else {
    console.log(...args);
  }
}

http.createServer((request, response) => {
  asyncLocalStorage.run(new Map(), () => {
    const store = asyncLocalStorage.getStore();
    store.set(requestKey, request);

    setTimeout(() => {
        log('timeout');
    }, 2000);
  });
})
.listen(8080);
Enter fullscreen mode Exit fullscreen mode

As you can see, when we wrap all our code within asyncLocalStorage.run, anywhere within the callback we can again retrieve any data we stored away. It's just like react's context API!

Building a nice abstraction layer around this is not too hard.


I'm excited about the possibilities this opens up. Here are some use cases I can think of:

  • Getting and setting cookies
  • Logging information about the current request
  • session flashing
  • Wrapping database calls in a transaction without passing the transaction object to each query (sometimes in separate function/class).

This was just meant to be a short introduction to this new feature. Obviously this also opens up room for complexity and dirty code. For example, it lets you access the request payload from anywhere in your application. Something you probably don't want to make use of in too many places as it couples the request with your entire code base. Well, let's see where this takes us!

Discussion (0)