The thing I love most about programming is the aha! moment when you start to fully understand a concept. Even though it might take a long time and no small amount of effort to get there, it sure is worth it.

I am of an opinion that the most effective way to assess (and help improve) our degree of comprehension of a given subject is to try and apply the knowledge to the real world. Not only does this let us identify and ultimately address our weaknesses, but it can also shed some light on the way things work. Simple trial and error approach may often reveal the elusive details that hindered our understanding of the matter.

With that in mind, I firmly believe that learning how to implement promises was one of the most important moments in my programming journey - it has given me invaluable insight into how asynchronous code works and has made me a better programmer overall.

I sincerely hope that this article will help you come to grips with implementing promises in JavaScript as well.


We shall focus on how to implement the promise core according to the Promises/A+ specification with a few methods of the Bluebird API. We are also going to be using the TDD approach with Jest.

TypeScript is going to come in handy, too.

Given that we are going to be working on the skills of implementation here, I am going to assume you have some basic understanding of what promises are and and a vague sense of how they work. If you don’t, here is a great place to start.

Now that we have that out of the way, go ahead and clone the repository and let’s get started.


The core of a promise

As you know, a promise is an object with the following properties:

Then

A method that attaches a handler to our promise. It returns a new promise with the value from the previous one mapped by one of the handler’s methods.

Handlers

An array of handlers attached by then. A handler is an object containing two methods onSuccess and onFail, both of which are passed as arguments to then(onSuccess, onFail).

State

A promise can be in one of three states: resolved, rejected or pending.

Resolved means that either everything went smoothly and we received our value or we caught and handled the error.

Rejected means that either we rejected the promise or an error was thrown and we didn’t catch it.

Pending means that neither the resolve nor the reject method has been called yet and we are still waiting for the value.

The term “the promise is settled”, means that the promise is either resolved or rejected.

Value

A value that we have either resolved or rejected.

Once the value is set, there is no way of changing it.


According to the TDD approach we want to write our tests before the actual code comes along, so let’s do just that.

Here are the tests for our core:

Running our tests

I highly recommend using the Jest extension for Visual Studio Code. It runs our tests in the background for us and shows us the result right there between the lines of our code as green and red dots for passed and failed tests respectively.

To see the result open the “Output” console and choose the “Jest” tab.

Jest's extension output

The result of the tests

We can also run our tests by executing the following command:

npm run test

Regardless of how we run the tests, we can see that all of them come back negative.

Let’s change that.


Implementing the Promise core

constructor

Our constructor takes a callback as a parameter.

We call this callback with this.resolve and this.reject as arguments.

Note that normally we would have bound this.resolve and this.reject to this, but here we have used the class arrow method instead.

setResult

Now we have to set the result. Please remember that we must handle the result correctly, which means that, should it return a promise, we must resolve it first.

First, we check if the state is not pending - if it is then the promise is already settled and we can’t assign any new value to it.

Then we need to check if a value is a thenable. To put it simply, a thenable is an object with then as a method.

By convention, a thenable should behave like a promise, so in order to get the result, we will call then and pass as arguments this.resolve and this.reject.

Once the thenable settles it will call one of our methods and give us the expected non-promise value.

So now we have to check if an object is a thenable.

It is important to realize that our promise will never be synchronous, even if the code inside the callback is.

We are going to delay the execution until the next iteration of the event loop by using setTimeout.

Now the only thing left to do is to set our value and status and then execute the registered handlers.

executeHandlers

Again, make sure the state is not pending.

The state of the promise dictates which function we are going to use.

If it’s resolved, we should execute onSuccess, otherwise - onFail.

Let’s now clear our array of handlers just to be safe and not to execute anything accidentaly in the future. A handler can be attached and executed later anyways.

And that’s what we must discuss next: a way to attach our handler.

attachHandler

It really is as simple as it seems. We just add a handler to our handlers array and execute it. That’s it.

Now, to put it all together we need to implement the then method.

then

In then we return a promise and in the callback we attach a handler that is then used to wait for the current promise to be settled.

When that happens either handler’s onSuccess or onFail will be executed and we will proceed accordingly.

One thing to remember here is that neither of the handlers passed to then is required. It is important, however, we don’t try to execute something that might be undefined.

Also, in onFail when the handler is passed we actually resolve the returned promise, because the error has been handled.

catch

Catch is actually just an abstraction over the then method.

That’s it.

Finally

Finally is also just an abstraction over doing then(finallyCb, finallyCb), because it doesn’t really care about the result of the promise.

Actually, it also preserves the result of the previous promise and returns it. So whatever is being returned by the finallyCb doesn’t really matter.

toString

It will just return a string [object PQ].


Having implemented the core of our promises we can now implement some of the previously mentioned Bluebird methods, which will make operating on promises easier for us.


Additional methods

Promise.resolve

How it should work.

Promise.reject

How it should work.

Promise.all

How it should work.

I believe the implementation is pretty straightforward.

Starting at collection.length we count down with each tryResolve until we get to 0, which means that every item of the collection has been resolved. We then resolve the newly created collection.

Promise.any

How it should work.

We simply wait for the first value to resolve and return it in a promise.

Promise.props

How it should work.

We iterate over keys of the passed object, resolving every value. We then assign the values to the new object and resolve a promise with it.

Promise.prototype.spread

How it should work.

Promise.delay

How it should work.

By using setTimeout we simply delay the execution of the resolve function by the given number of milliseconds.

Promise.prototype.timeout

How it should work.

This one is a bit tricky.

If the setTimeout executes faster than then in our promise, it will reject the promise with our special error.

Promise.promisify

How it should work.

We apply to the function all the passed arguments, plus - as the last one - we give the error-first callback.

Promise.promisifyAll

How it should work.

We iterate over the keys of the object and promisify its methods and add to each name of the method word Async.


Presented here were but a few amongst all of the Bluebird API methods, so I strongly encourage you to explore, play around with and try implementing the rest of them.

It might seem hard at first but don’t get discouraged - it would be worthless, were it easy.


Thank you very much for reading! I hope you found this article informative and that it helped you get a grasp of the concept of promises and that from now on you will feel more comfortable using them or simply writing asynchronous code.