Introduction to Asynchronous Programming with JavaScript

Async Programming Pattern with Callbacks, Promises and Generators

Subscribe to my newsletter and never miss my upcoming articles

Your plan was to drink some tea while you read a book. However, when you were about to start, you noticed that there was no sugar for the tea. Instead of completely aborting your plan, you send for some sugar, keep aside the tea and begin reading. When the sugar arrives, you are notified. You pause reading, mix up the tea with the sugar, and go back to your initial plan.

This is an Asynchronous Pattern. JavaScript inherently supports Async programming patterns with something known as Callbacks. Going back to our analogy, you were "called back" when your sugar arrived. You then proceeded to mix them up together.

Callbacks in JavaScript works kind of in the same way. These are functions which contains instructions/statements you want to run after the Asynchronous process is resolved. A callback is the unit of Asynchrony. When you see a callback in a code, know that part will not resolve immediately.

Callbacks are simply functions passed as an argument to another "Async" function.

They are set-up as continuation for processes which are Asynchronous in nature. A very good example would be an Asynchronous get request to an external API with a utility such as fetch ().

var data = fetch( "http://some.url.1" ); 
console.log( data ); 
// Error. `data` won't have the Ajax results by the time this runs. 

ajax( "http://some.url.1", function myCallbackFunction(data){ 
console.log( data ); // 'data' now has the results! 
} );

Let's break down that code snippet fellas.

  • The first function is asynchronously processed, i.e it runs later.This means that console.log() statement is going to run before the data is returned. The outcome being that 'data' variable will not have the results stored.

  • The second function makes an API call to a server. This indicates Asynchrony. You then define a callback which will be "called" when the Async process resolves. The callback can optionally take in the result of the Async process (data from API, in this case), work on it and return an output.

What actually happens under the hood?

What happens is that the JavaScript engine runs in a synchronous, single-threaded pattern.

Synchronous: It executes statements orderly, one after the other.

Single-threaded: Two instructions cannot run on the same thread, as is obtainable with languages like Java. One simple instruction runs, then another.

When you bring Asynchrony into the fore, some interesting things starts to happen.

The JS engine doesn't run in alone. It runs inside a hosting environment, which is for most developers the typical web browser and for many others, Node JS environment.

Whenever your JS program makes an Ajax request to fetch data from a server API, you set up the "response" code in a function (callback), and the JS engine tells the hosting environment that it is going to suspend execution for the meantime and when the environment is done with the network request and is with data, it simply should call the function back.

The browser is then set up to listen for the response from the network, and when it has something to give you, it schedules the callback function to be executed by inserting it into the event loop.

What is an event loop?

Consider this hypothetical piece of code I got:

var eventLoop = [ ]; 
var event; 
// keep going "forever" 
while (true) { 
// perform a "tick" 
if (eventLoop.length > 0) { 
// get the next event in the queue 
event = eventLoop.shift(); 
// now, execute the next event 
   try { 
      event(); 
   } 
   catch (err) { 
   reportError(err); 
  } 
 } 
}

Let's break down this code fellas.

  • 'eventLoop' is an array that acts as a queue (first-in, first-out)
  • the 'event' variable is initialized.
  • while...true ensures that the loop will run continuously and in it we use a conditional to check if the event loop has an item in it. That is, if there is a callback inserted to be executed.
  • The next task is gotten with the shift() method and executed in the try block. It is then removed from the queue. This happens continuously.

This is how an event loop works.

There are other important concepts to understand. These include run-to-completion, parallel threading and concurrency

All these topics will be discussed in future posts.

PROMISES

You went to the post office to get two of your packages. At the counter, the attendant tells you: "Hey, both your packages are currently unavailable but will be here in about 5 minutes. Can you please take a seat and wait a little? Thank you". That is a promise.

You go and take a seat. In a few minutes, your package arrives. However, you requested for two packages. Instead of calling you and handing over just one, the cashier waits for resolution and the order remains open. Once the second package arrives, you are then now called and handed over your complete package. The order is now fulfilled (or resolved with a success message). Had any or both of the packages not have arrived, the order would have been rejected (or resolved with a failure). When the message (or package) is delivered, it becomes immutable. So you cannot go back and claim the package was opened before handed over to you.

Sorry for the long story, just hope you have an idea of how it works.

There are two key things to note from my explanation:

  • Promises are time-independent. This means that if there are say two or more Async processes, a promise will wait for ALL to resolve before resolving itself.

  • When promises are returned, they become immutable. You can then only alter their contents in the chained handlers which can be resolve() or rejected () handler.

The handlers are defined in the chained then() method. If the promise resolved with a success, the resolve(), or the first function is called. If an error occurred (rejection), a reject() or second function is called.

function add(xPromise,yPromise) { 
// `Promise.all([ .. ])` takes an array of promises, 
// and returns a new promise that waits on them all to finish

return Promise.all( [xPromise, yPromise] ) 

// when that promise is resolved, let's take the received `X` and `Y` values and add them together. 
.then( function(values){ 
// `values` is an array of the messages from the 
// previously resolved promises 

return values[0] + values[1]; 
} ); 
} 


// `fetchX()` and `fetchY()` return promises for 
// their respective values, which may be ready 
// *now* or *later*. 
add( fetchX(), fetchY() ) 
// we get a promise back for the sum of those 
// two numbers. 
// now we chain-call `then(..)` to wait for the 
// resolution of that returned promise. 
.then( function(sum){ 
console.log( sum ); // that was easier! 
} );

The above code is fine, but it's missing one component. What if the promise got rejected? What if an error occurred while adding up the two values?

That's when we'll need to define a second function for error handling:

add( fetchX(), fetchY() ) 
.then( 
// fullfillment handler 
function(sum) { 
console.log( sum ); 
}, 
// rejection handler 
function(err) { 
console.error( err ); // bummer! 
} 
);

Note that you can defined a catch method at the bottom of the chain and avoid having to include rejection handlers on all then() methods.

N/B: If you want to learn more about Callbacks and Promises, and see it in action, I'll suggest you get the HTML to React Course by SleeplessYogi

There is still a lot to learn about Promises. But this covers the basics about it.

Generators

We've so far learned that we can implement Asynchronous patterns in our JavaScript with callbacks and promises, there's one new addition to that list: Generators.

Remember when I mentioned run-to-completion? Time to get into it.

All normal functions in JavaScript have run-to-completion. This means that the JavaScript engine will execute all statements in one function before doing anything else.

Here's the flow in a normal function

function myFunction(p1, p2) {
  console.log(p1 + p2); // runs first
  return p1 * p2;   // runs after
}

Generators are functions which do not obey this principle. These functions can pause in the middle and "yield" for another function to execute before resuming it's own execution.

function *foo(x) { 
var y = x * (yield "Hello"); // <-- yield a value! 
return y; 
} 
var it = foo( 6 ); 
var res = it.next(); // first `next()`, don't pass anything 
res.value; // "Hello" 
res = it.next( 7 ); // pass `7` to waiting `yield` 
res.value;

Let's break this code down fellas

  • The asterisk (*) indicates that this is a generator function, not a normal function.

  • The yield statement signals the first "stop point".

  • The iterator of the function is instantiated with an argument and stored in the variable

  • The iteration is kick-started with the first next() call. You don't pass anything here. When this is done, engine starts executing then stops at the next pause.

  • See that in the yield statement, a placeholder value is defined. This value will be replaced by whatever we pass to the second next() call, which is 7.

  • That call also resumes iteration and the function resolves to 13, as showing with the value property.

Generators can be very useful for Asynchronous programming. A good use case of generator would be to yield for an AJAX call to an API before resuming the function with the returned data.

If you liked or benefited from this article, consider buying me my favourite fruit:

Will appreciate that a lot.

Thank you and see you soon.

@thebarefootdev's photo

I enjoyed the analogy of the tea, that was an excellent introduction. I felt that the article became a little too involved in the middle, your explanations were a little confused and not quite exact. Regardless a good overall article if lacking in providing the other common approach of using async await to avoid chaining callbacks and ending up in the now infamous callback hell.

Adrienne Tacke's photo

Excellent analogy with the tea! :)

Kingsley Ubah's photo

Thank you!

Glad you liked it.

Koga Ryu's photo

Promises and generators, you honestly lost me. Promises - where does this fit in in the whole? Generators - Yes I get that the generator iterates similar to a linked list in C++, but where does this fit in to Async matters?

I suspect what I wanted is a grander example showing how these work together in a coffeeshop or a post office to achieve whatever end they are ment to.

I think the reason I fail to user is how the moving parts fit together once in motion.