An Illustrated Journey Into Promised land

Journey into Promised Land. Doodle on paper — 2017

Anything that is asynchronous by nature lends itself to being asynchronous in its understanding as well — Tea MP, 2017

Some things are best understood by example. Some things require careful exposition from subject matter experts. But few things are not understood easily because they defy our natural way of thinking.

Asynchronous actions fall into this category. As a developer, you are bound to “eventually” and inevitably face this way of thinking. The reason why async is hard is because human beings are good at handling things serially. Concurrency or parallelism is a foreign concept, much like 4 dimensional space. This post is aimed at creating a useful guide that will help someone understand how asynchronicity is handled with “promises”.

Illustrations will guide our journey into the Promised land. I promise you that at the end of this post, you will end up with a much better understanding of how promises work. Then, you either achieve fulfillment of my promise or reject the notion that a promise denies understanding.


A call back from home

Before we begin, buckle up your seats for a short overview of an asynchronous function.

In this example, the result of the first statement is 3 whereas, the result of the execution of the second statement is undefined.

Why is this the case? The second function adds the numbers after 2 seconds, it does not return anything. Hence, Javascript denotes the return type of the function as undefined. But, what if we want the result that is obtained after 2 seconds? The solution is to define a callback function.

A callback function is that which when provided can be called at a later period in time.
Call me maybe..

After 2 seconds, it prints 3 to the console. Note that the function asyncAdd still returns undefined. Upon calling an asynchronous function, its results are explicitly available through a callback function.

The callback in the asyncAdd function can also be called immediately, instead of inside the setTimeout. In this case, it acts as a synchronous callback. As a result callbacks do not necessarily imply asynchronous actions. The important point to remember is that the callback may be called at some time in the future.

What if we want to communicate to the caller that something went wrong ??

In this example, we define two callbacks, one for reporting a successful operation and other for reporting an error. We can also use a commonly adopted convention which uses a single callback instead:

One callback to call them all..

The single callback accepts two arguments, the first one corresponds to an error, while, second one corresponds to the actual result. This style of using callback(err, data) is extremely common thanks to the NodeJS ecosystem.

For those who are acquainted with the asynchronous nature of NodeJs, it is easy to understand the frustration of writing callbacks inside callbacks which then nest deep under another set of callbacks. Phrased as callback hell in the NodeJS community, it is not hard to see why following the flow of code is a frustrating affair.

It is quite common to have multiple asynchronous operations within a single request to a server. e.g consider the Amazon e-commerce product page: given a query it has to fetch matching products, get ratings from an external service, find similar products etc. If we assume that all of this has to happen within a single request, the flow would be as follows:

  1. Find the most relevant product given a search query (for simplicity assume there is only one such product) e.g canon camera.
  2. Get ratings from an external service productRatingService
  3. Then Find similar products similarProductsService (which also takes in account the rating)

This translates to code as follows:

Look at that nested structure

In the above code snippet, note how the callback function for ratings and similar products is nested inside the callback of the outer function. The inner functions must all then complete running before the final result is returned. What if the similar product service needs to call something else in turn?

The nested callbacks result in a pyramid structure which is extremely hard to deal with.

<>
>
>
>
>
>
\\(0_0)//

To see this in detailed action follow below:

Callback hell

In the next section, we introduce the notion of a Promise, and how it can convert the nested execution flow into a flat one. For all of the examples from now on, the functions will return a Promise object. We now use a magic function promisify to convert a callback style function into a promise.

var functionReturningPromise = promisify(functionAcceptingCallback);
// Executing functionReturningPromise returns a Promise object

What exactly does promisify do ? Well, it returns a function which when executed returns a Promise object. We will explore this function in detail later.

The promise of a future

A promise represents the eventual result of an asynchronous operation — Promise spec for A+ grades

Anything that can eventually return a result can be represented using a promise. The result of the action is available using the promise’s then method.

A promise resolves to some value or is rejected with some reason “some time” in the future.
The typo is intentional.. to make sure you read code :)

The then method accepts two callbacks. While, first method is called on success with the result, second is called on error with the reason. Both functions are optional, if missing, they are ignored. If the asynchronous operation executes without any hiccups, the success callback is called. But if it throws any error the failure callback is called.

In the above example, if the ask pharaoh promise fails then onFailure callback is called. It is important to understand that failures need not always be due to Errors thrown in JS runtime. A promise can simply be rejected if some condition is not met, e.g if (iAmAngry) reject(“I am angry"). Here onSuccess is the success handler, and onFailure is the failure handler.

Notice that the handlers passed into the then method are handlers for the “previous” promise, that is askPharaohWithPromise.

A promise can also resolve some value that is already available. But the resolution is not “immediate”, it is still asynchronous. More on this later.

getFortyTwo().then(function (result) { 
console.log(result); // prints 42 (asynchronously)
})

What happens then ?

The then method always returns a new Promise. This is a very crucial point to understand. The inputs to the then method are success and error handlers for the previous promise, but the method itself returns a completely new Promise.

Click here for reference.

Good ol days where promises were kept

Returning a new promise allows us to chain a new promise to an existing one. A Promise that is thenable, i.e has a then method, can be chained to create another promise.

promiseA.then().then().then()….

The second then method accepts success and failure handlers for the promise returned by first then method. Third then accepts handlers for promise returned by second then and so on.

Pearl of Wisdom… Keep in mind that the callbacks that you pass to the then method are the success and error handlers of the previous promise. The then method itself returns a new Promise whose handlers must be defined in another then .

The State of Promise

A promise can have three different states. Unlike Schrodinger’s cat, it can only be in a given state at a given time.

The following are allowed promise states:

  1. Pending
  2. Fulfilled
  3. Rejected
Is it dead or alive ? 50–50 ?

Click here for promise states.

A pending promise can transition into one of the other two states.

  1. A promise is by default in “pending” state.
  2. Once a promise is fulfilled, calling “then” on the promise will yield its value. Calling “then” again on the promise will yield the value without performing the actual async operation again. This is useful because if the promise is fetching some data remotely, the returned promise acts like a simple data cache.

But if remoteDataCall is called again, the remote data call happens, because every time the function is called, it returns a new Promise.

Pearl of Wisdom… If you are invoking the same asynchronous action multiple times in several places in close succession, consider returning the original promise. But regular cache invalidation rules apply.

2. Once rejected, the promise will always stay in that state. But what about error recovery ? This is where promise branching comes to the rescue. Read more in the promise branching section.

The value of a Promise

What values can a promise return?

  1. A promise can return undefined or null
  2. A promise can return any Javascript Object. (Number, String, Function).
  3. A promise can return another promise.

In case of 2. since Javascript Objects and Functions can have properties, if the return value has a property called then , then it is treated like a promise.

An example of 3:

Go bananas..

The result of promise would be the same as executing getSomeBanana(10). What if another promise is chained to it ?

Make up your mind..

The result of promise is now the same as executing getSomeStrawberries(bananas.length)

Thus, not only can promises be chained together, they can return other promises nested inside the success or error callbacks. But wait… what is the difference between nesting and chaining? In other words, what is the difference if the above snippet is changed to:

I like both..

The callback functions are called implicitly with the result from each previous function. In this case getSomeStrawberries gets called with bananas as the argument.

Promise chaining thus allows us to express the nested workflow as a flat one.

getRelevantProduct(searchName)
.then(productRatingService.getProductRating)
.then(productRatingService.getSimilarProducts)

In the above example, the first promise getRelevantProduct returns the product which is then passed onto rating service and then passes the product onto getSimilarProducts. Notice the flat structure compared to the pyramid structure with callbacks.

<>
>
>
>
>
<_(o.o)_>

But what about:

Looks like that familiar callback hell..

If everything goes well, it behaves just like before. However in this case, errors do not propagate correctly. If getSomeBananas or getSomeStrawberries throws an exception, there is no handler to catch their errors. Why? Remember that error handlers are defined by using .then and then passing the callbacks inside.

Since there is no error handler defined for the nested promise, will the error method catch it? No, it does not. The error method is responsible for catching errors caused by doSomethingAsync (i.e the “previous” promise). It will not capture errors emitted by the success handler, which is its sibling. This will become more clear in the Promise Branching section below.

For more details, follow Nested Promises section in:

Promise Antipatterns

Pearl of wisdom… Prefer chaining to nesting.

Promise branching

The success and error callbacks combined with the then method allows us to define promise branches. A branch is simply a path undertaken in a promise chain. Once again, we return to an illustration:

(Mango) Pulp fiction indeed

In the above diagram, you can see the different paths that an unfold as you go from craving mangoes to finally eating mangoes. Depending on the outcome of one box, a path can deviate into another “branch”. For our discussion, this is the definition of branching.

How does this look in code ? For simplicity, we turn our brains off and assume that each box returns a promise.

Depending on dad’s mood, dad is going to give money or refuse. Also a thief might attempt to steal mangoes after you buy them. But for now, let the thief wait till it is midnight.

dadBranch()returns a path that can be followed in case of failure. Ask Dad for money, if he gives money, go and buy mangoes, else sit and cry. This is nothing new, you are already familiar with the success and failure handlers passed to then method.

But what happens when something goes wrong with buying mangoes?

It is night time, and the thief is out to get you. If a thief is present, buyMangoes will throw an exception which causes the promise to enter rejected state. Now an important question: if this error is thrown, does askDadForMoney chain execute?

It does not. This is very important to understand. If an exception happens in the fulfillment handler or error handler, it is only passed to the next handler in the promise chain.

Mango Thief..

If there is no handler, the error is simply swallowed. There is no warning, no log and no reason to think something went wrong. The error is simply silenced.

Pearl of Wisdom. Exceptions that happen in callback functions passed to a promise’s then method are caught and handled by the next handler in the chain.

Certain promise implementations use done method to get around this problem, but is non-standard. For more details see here:

Therefore, a promise that is resolved can return a new promise that is rejected, simply because an error happened while executing the callbacks.

Notice the branching..

The opposite is also true. If an error handler successfully handles an error, the success callback of the next handler in chain is called. e.g.

parseJSON for all..

Here serverNotAvailable is responsible for handling the error, and stepping in, providing sample mock data.

Now for the Catch

Promise branching is a great way to think of asynchronous workflows, with error handling a part and parcel of the promise chain. If my promise throws an error and it is handled by the next handler, then I can resume my workflow as usual. But there is another school of thought dubbed “single exception channel”.

A single exception channel calls for using a single error handler for the entire promise chain
One catch to handle them all..

In this case, the error handler catches all of the errors that can happen during *any* of the asynchronous actions, i.e. there is only a single exception handler. Here, any error that happens during execution is simply caught by the catch handler.

The “single exception channel” approach is great if any of your actions end up throwing an error and you want the entire workflow to fail at that point. For example, if the first promise is writing to a file and the second promise is about compressing that file, the second one should not run if first one fails.

On the contrary, if you want to catch and resume, you can use the regular .then(onSuccess, onError) model. e.g. Attempt to read data cached in a file > file does not exist > Simply ignore and move on.

Pearl of Wisdom… Use the single exception channel if any action results in a fatal error in your workflow. If there are points in your workflow that you can resume using a recovery mechanism, use the success, error callback model.

Often a combination of both work well for large workflows. But if your workflow is big, maybe it is better to split it into chunks of smaller workflows.

Did we forget something? What if the .catch method throws an error? As before, it will be swallowed. Once again, the error gets silenced. If you need to throw errors from your catch handler follow the advice here:

The Async Requirement

There is something very important that must be considered when implementing a Promise library. The promise callbacks are called only after the execution context contains only platform specific code. To put it simply (and a little vaguely), this means that the handlers only start executing after the next tick of the event loop. Why is this important?

Who wins ?

In the above example, the log prints the values until 100000, before the promise value is resolved. Thus, even though getFortyTwo immediately resolves to value 42, it still executes at the next turn of the event loop. This gives the guarantee that any promise that returns values synchronously will still run after other synchronous operations. Why is this important?

It gives us the guarantee that any synchronous operations in code that are interleaved with asynchronous operations always execute first.

Something something..

Here, the execution flow should look like

doSomethingSync > doSomethingElseSync >doSomethingAsync .

If for some reason, the async promise returns synchronously, the execution flow would be

doSomethingSync >doSomethingAsync > doSomethingElseSync.

This creates confusion. By making doSomethingAsync execute asynchronously, the former order of flow is maintained.

Promise libraries typically use a queue or “trampoline” to get this behaviour.

Bounce away..

One Promise, Many Handlers

Each promise can have multiple callbacks attached to it using then. We already saw this for the data cache case.

Many handlers..

Each callback is called in the order it is attached to the promise. So success2 is called after success1. Promise libraries, thus, ensure a consistent ordering of callbacks, to reduce surprise.

Promisification

So far, we talked about promises, branching, exception handling and chaining. But how exactly do we go from a callback style function into a promise. There are two approaches to take:

  1. Promise constructor
  2. Deferred approach

Promise constructor

In this approach, the callback function returns a new promise as follows:

Gist is fetching some JSON ?

The promise constructor accepts a function which in turn is passed a resolve and reject function. The constructor wraps the asynchronous function so that any errors that are thrown during function call are automatically caught.

Deferred Object

A deferred object has three components: a new promise, and a resolve and reject method.

var deferred = new Deferred();
deferred -> { promise, resolve, reject }

Theresolve and reject methods directly affect the state of the promise that it holds. So, what exactly is the use of a deferred object ? We use it like shown below:

That doesn’t look very different..

Wait! It looks almost the same as the promise constructor approach. The difference is that the deferred object does not catch any errors that are thrown synchronously by getSomeJSON. The Promise constructor wraps the call to getSomeJSON and as a result, any errors that are thrown are properly handled. But, in the deferred case, it only handles errors that are handled correctly by getSomeJSON.

The magical promisify function that we mentioned in the beginning, works by using one of these approaches.

Pearl of wisdom… Use promise constructor approach for most of your needs. If your promise library gives you a promisify function use that. If somehow both of these approaches fail, use deferred approach.
var applicationFn = Promise.promisify(libraryFunction);

See more here: Deferred Anti Pattern

Towards the End..

Before the journey ends, lets take a quick recap of the salient points that we covered so far:

  1. Promises represent eventual result of an asynchronous operation.
  2. Promises can be chained together allowing flat execution flow control.
  3. Promises can return any value, including another promises.
  4. Promises always execute asynchronously, even if the value is immediately available.

References:

  1. A+ promise spec or the website.

Useful Reading:

  1. Matt Greer.. Promises in wicked detail
  2. Pinky Promise , writing a promise library in 45 lines of code
  3. Promise Antipatterns
  4. Functional programming and promises
  5. Bluebird wiki
  6. Google on Promises
  7. David Walsh on Promises

Some promise libraries:

  1. Q
  2. Bluebird
  3. ES6 Promise and its polyfill: ES6 Promise polyfill

Apologies for the mix of images, gists and inline code blocks. Different sections were created at different points of time (asynchronously).

Illustrations created with good ol’ pen and paper, and edited on Galaxy A5 2016 phone.