How to make the fastest Promise library

Suguru Motegi
HackerNoon.com
6 min readApr 3, 2017

--

I have developed Aigle which is a fast Promise library. It is inspired byBluebird. The library is not only a benchmark exercise but a production-ready library that implements the Promise A+ standard, and does so faster than Bluebird.

What are Promises?

Before explaining it, I would like to give basic information about Promises. A Promise can have three states: pending, fulfilled and rejected. Once the state goes to another state from pending, the state cannot change again.

Promise states (quote: MDN)

Besides, Promises must always resolve asynchronously, this is one of the most important things.

Best practices for high-performance libraries

When I develop libraries, I always follow these three principles.

  • Avoid creating unnecessary variables, functions and instances
  • Avoid executing unnecessary functions
  • Deal with asynchronous function smartly

Avoid creating unnecessary variables, functions and instances

Following this principle helps avoiding unnecessary memory allocations. For example,

When sum is called, the iterator function is always created. It is one of unnecessary memory allocations. The code is rewritten to follow next example.

The code avoids making unnecessary functions.

Next is another extra example of memory allocation.

In this case, string is the only required variable. array, object and number are unnecessary. The code is rewritten as below,

There is not a big difference between the examples, but if instances or functions are created in the function, it would make big difference.

Avoid executing unnecessary functions

For example, when creating APIs, it is necessary to check the request parameters.

When implementing APIs, it is preferable to check the arguments in inner functions because the function might be called from other functions. However, when implementing libraries, the function doesn’t need to check the arguments. Before executing inner functions, the arguments are already checked, so it isn’t necessary to check them again in inner functions.

Deal with asynchronous function smartly

I would like to explain this concept with Bluebird examples.

What makes Bluebird fast?

If you open Bluebird library code, you will see bitField parameters. But the bitField is not so important.
Bluebird has roughly two states: pending or not. The big difference in handling is between the two states.

If the state is pending:

This execution order is,

  1. Make a parent instance of Bluebird when new Bluebird is called
  2. Execute executor
  3. Execute then.then makes a child instance of Bluebird
  4. Execute resolve
  5. Execute onFulfilled

When then is called, a child instance is created. At that time, resolve is not called yet, so the parent’s state is pending. When the state is pending, the child instance is linked to the parent instance as a child. After that, resolve is called asynchronously by setTimeout. And then, onFulfilled is called with the result.
When state is pending, a child instance is linked to parent instance. After resolve is called, onFulfilled is called. It is very simple.

If state is not pending:

The execution order is

  1. Make a parent instance of Bluebird when new Bluebird is called
  2. Execute executor
  3. Execute resolve
  4. Execute then. then makes a child instance of Bluebird
  5. Execute onFulfilled

When then is called, the parent’s state is already not pending. In this case, if onFulfilled is called without anything, the function is executed synchronously. For that reason, the library needs to call an asynchronous function. Before calling the function, onFulfilled is set to a queue and this schedule function is called. The schedule is setImmediate on Node.js. And then setImmediate calls all queued functions asynchronously.
When a state is not pending, onFulfilled is set to a queue, and then queued functions are executed by asynchronous functions.

You might be wondering why onFulfilled is set to a queue. This is the smartest idea in Bluebird.

How to handle the asynchronous function smartly

I would like to explain it with this example.

If a queue is not used, an asynchronous function is called three times. But if a queue is used, the function executes all queued functions at once.
I’m not sure if the bitField gives a big benefit or not. But I think why Bluebird is fast is because of simplicity.

What makes Aigle fast?

I have just followed these important principles,

  • Avoid creating unnecessary variables, functions and instances
  • Avoid executing unnecessary functions
  • Deal with asynchronous function smartly

If you follow them, you will be able to make good libraries. I made a benchmark to check performance between Aigle and Bluebird. The benchmark result is here.

  • Node v6.9.1
  • Aigle v0.5.0
  • Bluebird v3.5.0
Aigle vs Bluebird

Aigle has very simple implementation, therefore it is faster than Bluebird. If you are interested in Aigle, I would like you to contribute to it.

Intended for production environment

The most important part isaigle-core dependency.
In Bluebird, every promise instance has to be an instance of Bluebird. When the instance is checked, instanceof function is called. But Bluebird only checks if the instance is made by the current Bluebird class or not. So if many versions are used, Bluebird will be slowed down.
The key to avoiding losing performance is to have same dependency. Aigle has aigle-core dependency, therefore every Aigle instance is extended by same AigleCore class. Aigle will keep high performance.

I would like to show the benchmark example.

As you can see, different aigle dependencies have same aigle-core sub-dependencies. When aigle@0.4.0 is used, the aigle-core is shared.

  • Node v6.9.1
  • Aigle v0.4.0, v0.5.0
  • Bluebird v3.4.6, v3.5.0

promise:then:same was using same versions, and promise:then:diff used child instances of different versions. Aigle never slows down even if the versions are mixed.

Conclusion

If you follow the three important principles, you will make a fast library.
Also Aigle has many functions inspired by Async and Neo-Async. If you still use callback style, I would like to encourage you using Aigle.
If I contribute to Node.js and JavaScript communities, I would be happy as a contributor.

Reference

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!

--

--