Tutorial to Native Node.js Modules with C++. Part 3 — Asynchronous Bindings and Multithreading

I thought JavaScript is single threaded?

Vincent Mühler
6 min readDec 1, 2017

By now we should have a basic idea about how to write a native Node.js addon. In this tutorial I am going to show you something quite awesome in my opinion. We probably all know that JavaScript in the browser runs in a single threaded environment and in case you want to load off some work to a seperate thread, you have to mess around with web workers.

Node.js however, per se is not single threaded. If you are interested, here is a link to an interesting post I found about asynchronous Node.js: How does Node.js work asynchronously without multithreading?. Not only is it possible to write bindings to a native Node.js addon, which are non-blocking / asynchronous, you can furthermore easily make use of multiple threads to perform computionally heavy tasks in parallel and in the next few minutes I am going to show you how to do so. As always, the example code can be found on my github repo.

So what’s the plan for today?

To prove, that our example is actually async and that it’s running on different threads we are going to implement a simple function, which will take a task id as a string as well as a number of iterations, which we will use to mimic long running tasks via a busy waiting loop. Therefore we are creating a helper function:

Note, that I declared i to be a volatile int in order to prevent the compiler from optimizing (e.g. removing) this stupid bit of code. The implementation of the synchronous function looks as follows:

Furthermore we will add an asynchronous version of the function:

First thing to mention here is, that we will give it two more parameters. Obviously we need a function callback, which is invoked, once the async task is done. I also added a boolean to indicate whether the AsyncWorker should throw an error, just to show that to you as well. At the bottom we can see how to spawn a Nan::AsyncQueueWorker, which takes as input any instance of a class that implements the Nan::AsyncWorker interface. For that, we will implement a MyAsyncWorker class, which we are going to instantiate with the parameters passed to the function.

Implementing the AsyncWorker

In order to implement an AsyncWorker, we have to define a class, which extends the Nan::AsyncWorker and implements Execute, HandleOKCallback as well as HandleErrorCallback:

In the constructor of MyAsyncWorker we will set all arguments passed to the function call as internal fields. If you are not familar with this syntax: “: Nan::AsyncWorker(callback)” don’t worry, this just means: call the constructor of the superclass with callback.

The Execute function will obviously perform the main work of our task, thus we are calling our delay function here. Furthermore, if you want to indicate that an error occured during Execute, you can call this->SetErrorMessage(), with an error message.

In the HandleOKCallback we will return the result. Well we do not actually perform anything meaningful here, thus we simply return the workerId and we will see later on, why I introduced an argument to identify the worker. HandleErrorCallback will be called instead of HandleOKCallback in case you did call this->SetErrorMessage(). Here we can return a meaningful error message. Later on in the JS callback we can check if an error (argv[0]) or a result (argv[1]) is returned.

Also note, that we will not touch any v8 related stuff in the Execute function, as this will cause nasty exceptions. We will wrap the return values in the HandleOKCallback or HandleErrorCallback. Also, we have to declare a Nan::HandleScope at the beginning of the scope, to prevent any created v8 objects to be garbage collected as soon as we leave the function scope. We never did this in a NAN_METHOD, since a Nan::HandleScope is implicitly given here.

So that’s how you write an async binding. Let’s see it in action!

Now let’s require our bindings and play around with it. But first of all, we will wrap the async function binding in a Promise, since we would have to invoke it with a callback otherwise, which is very inconvenient once you make multiple asynchronous calls:

In case you are not familar with Promises: Promises are basically a convenient way to chain asynchronous calls or to wait for the termination of multiple asynchronous calls, which are running at the same time. With promise.then() you specify what happens next when the Promise resolves with no error and ith promise.catch() you can catch rejected Promises. Thus doAsyncStuffPromised returns the Promise that resolves when our doAsyncStuff holds a result and rejects if an error occured.

We will define runSync and runAsync to run our sync and async bindings a few times respectively and log the execution time with console.time:

runSync:

runAsync:

In the synchronous version we just call the function numTasks times synchronously and the asynchronous version we will create numTasks Promises. Each Promise will log the taskId when it’s execution has finished. With Promise.all(promises) we can run all Promises in parallel and once all Promises resolved, we will stop and log the recording time. Keep in mind, that you might want to adjust the number of iterations to increase or decrase the delay, which is specific to your CPU.

Running the synchronous code:

runSync(4)
...
task syncTask_0 finished
task syncTask_1 finished
task syncTask_2 finished
task syncTask_3 finished
execution time: 7000.121ms

If we run the synchronous code, we will get something like the output above. Every task seems to finish after it’s predecessor and execution will take some time. That’s what we would have expected.

Running the asynchronous code:

runAsync(4)
...
task asyncTask_2 finished
task asyncTask_1 finished
task asyncTask_0 finished
task asyncTask_3 finished
all tasks finished: [‘asyncTask_0’, ‘asyncTask_1’, ‘asyncTask_2’, ‘asyncTask_3’]execution time: 2207.185ms

Okay… what the heck is going on here? Well the first thing we can see is, that it took much less time to execute the asynchronous tasks, more precisely the async tasks finished roughly 3 times faster than the synchronous tasks.

Effectively creating a new AsyncWorker will spawn a new Thread. Another interesting phenomenon, which you will probably already have encountered when writing multithreaded programs, is the race condition, which we can see in the logged output. The tasks are not necessarely finishing in order, although we invoke them in order and the “work each task has to perform” is the same.

The output saying “all tasks finished:” also shows how the result values of the Promises in the array we passed to Promise.all are returned. Just as you would expect, they are returned in an array as well.

Triggering and catching an error:

Now if we set the throwsError parameter to true, we would expect an error to be thrown, which we should be able to catch. So running the following:

console.log(‘Expecting an error message soon:’)
doAsyncStuffPromised(‘foo’, true)
.catch(error => console.log(error));

will infact yield us the output:

Expecting an error message soon:
An error occured!

Great! Seems to work.

Conclusion

And that’s it for the last tutorial of this article series. If you followed the series up until this point, you probably have achieved the fundamental skills to write your own native Node.js addon. I hope this series was helpful for you to ease into native Node.js development. If you are currently working on some project or have built something interesting with the native Node.js API simply leave a comment below. I would love to here about it!

Need an introduction to developing native Node.js addons? Check out Part 1.

Want to know how to deal with arrays, JSON objects and callbacks? Check out Part 2.

--

--