Up and Running with Asynchronous JavaScript

A head start to the most misunderstood aspect of JavaScript

Rafael Cepeda
Sep 9, 2018 · 5 min read

Loosely, synchronous means at the same time while asynchronous means not synchronous or not at the same time.

To illustrate, imagine you call a friend and ask them to find the cheapest flight to Hong Kong. This can take some time, so you ask him to call you back when he has information (no sense in waiting since you could be doing something else).

If you waited on the phone, you would’ve been synchronous, but during the “call-back” scenario, you are asynchronous. The async scenario proved its benefits, allowing you to take advantage of your friend’s time, the other execution context.

In JS

To understand async calls in JS, you should understand JS’s execution environment. JavaScript executes in an event-loop, which is the same situation you’re in; JavaScript has one thread of execution, the main thread, just as you did in the scenario above, but JavaScript’s engine has multiple threads, however, just as you have multiple friends who could work for you.

When JavaScript needs to do some tedious task, like busy-waiting network io, it asks its friends too, the other execution contexts. When this translates to code, however, the only way for JavaScript to interrupt the main execution context, is to call some function when an event is raised. This is a call-back function, which is to do something when the friend has information for you.

JavaScript has evolved and has many ways to handle async scenarios, but the notion is the same: call a function later.

Old-School JS

A decade ago, event-handler functions were all you heard of, you didn’t have promises, async / await, or fancy observables, just a function to call when an event was raised:

You still see them around, once in a while, especially when coding using the standard APIs.

Things get more interesting when you have an async handler that depends on another async handler. How would you handle this?

scant attempt to merge event-handlers

When things get crazier, say n amount of events, things have to get more creative, more generic, and maintaining state becomes annoying. What we need is an abstraction that hides these details, is generic, and ultimately encapsulates this.

New-School JS

Promises

The abstraction provided by the community was Promises, an easier way to manage async. Libraries each had their own implementation of Promises and things became annoying here too. Luckily, JS evolved and introduced native Promises:

Promise.prototype.then() has a functionRef parameter, to call later as classic event-handling did via assignment.

The unfortunate thing about Promises is that they only work once. Once they are resolved, they are fulfilled; the code above only works for the first click event of both elements.

The community, by and large, didn’t understand Promises — they still don’t. I have seen many anti-patterns. Here’s the grievous most-encountered:

The issue with this implementation is that Promise.prototype.then returns a new Promise resolved with the return value of the functionRef callback and can be elegantly expressed as:

Async / Await

The community wanted a more elegant way to deal with Promises, so js introduced async and await keywords to hide promises:

The promise is still there, it’s just syntactic-sugar.

Promises are great for one-off scenarios like making a request, but they fail when you need to handle async over time. So, once again, the community has brought us an abstraction: Observables.

Observables

Observables spawned from the popular Observer design pattern which publishes values to subscribers. Observables are much more powerful, however, they provide ways to transform data as it flows, like a stream, from source to destination. RxJS provides many operators to transform data and you can even define your own.

Here’s how RxJS Observables solve our previous one-off issue:

That’s pretty elegant. Let’s filter clicks in the event stream:

You can create, transform, filter, and combine Observables.

Observables become particularly useful when you have a single app-store; when the store is updated, every subscriber is notified and can transform the data as it needs and perform side-effects.

WebWorkers

Wrapping a block of code in a Promise doesn’t make it async, so what determines the async nature of something? After all, these are only abstractions to handle something that is async not define something that is.

The JS engine handles certain operations on separate execution contexts without your consent, like event handling, ajax calls, etc., but wouldn’t it be nice to define your own?

The WebWorker is a JavaScript file that ultimately defines a function that is async to threads outside of it. (This is much like the phone call we made to our friend to look up tickets and call us back when he had an output for us.)

This keeps the main thread nimble by only doing view-related tasks. The principal difficulty with WebWorkers is making them generic enough to handle a variety of inputs and keeping them around long-term, as spawning them sporadically may outweigh the benefit of having them at all.

Showing you how is outside the scope of this article, but you can enlighten yourself from any respectable operating systems book.

Conclusion

At the heart of async, is the notion of a call-back function, or callback, a function reference to be called at some later time. This has lead to elegant abstractions like Promises and Observables. Promises are one-off async handlers, while Observables are async handlers over time. RxJS beefs-up the Observer pattern by providing operator functions that transform data in the event stream and are particularly useful when an update to state must be propagated. Do Observables diminish Promises? Certainly not, as arrays don’t diminish scalars, as plurals don’t replace singulars, different abstractions are used to convey different concepts.

Promises and Observables provide elegant ways to handle async contexts not define them. To create our own execution contexts on the web, we create WebWorkers. Although the APIs for using them is classical, we can leverage abstractions to keep things elegant by hiding complexity. Lastly, the real power of WebWorkers is realized when their workflow is generalized to keep them around long-term, keeping the main thread responsible for UI-only tasks.


Originally published at gist.github.com.

Rafael Cepeda

Written by

Spending my youth late-night hacking, family, friends, and teachers realized I had an inclination toward taming the bytes that produced results. I was in…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade