Javascript – Asynchronous Programming

JavaScript engine with the stack, queue, events, and APIs
The event loop takes work from the queue and passes it to the stack

Apart from a few legacy bits that linger like the alert() function above, JavaScript is an asynchronous language. 

When an event fires, all that happens is a new message is added to the queue. No event has the ability to take over the thread. Each event fired must get in the queue and wait its turn to run. 

One way to illustrate this is by using the setTimeout function. In this example, invoking setTimeout, we pass an event handler and a timer in milliseconds. When the timer is up it fires, adding the event handler to the queue. 

setTimeout(function(){
  console.log("This comes first");
}, 0);
console.log("This comes second");
//output in console
// "This comes second"
// "This comes first"

Here we’ve set the timer to zero. But this doesn’t mean “call right away.” It just means “put this in the queue right away.” However the execution of the block of code itself needs to finish, clearing the call stack. Only then is the function from setTimeout given its turn. 

Another common mistake is to think the timer is an exact predictor of when the event handler will fire, but it isn’t necessarily. The event handler still has to wait its turn in the queue. By measuring time, we can see this in action

const timer  = function(){
  let start = Date.now();
  setTimeout(function(){
    let end = Date.now();
    console.log( "Duration: " + (end - start) )
  },1000);
};
timer();
// Console output when invoked several times:
// "Duration: 1007"
// "Duration: 1000"
// "Duration: 1002"
// "Duration: 1004"

The time is set to one second, and it runs pretty close to that. But clearly there is some variance in how quickly the function can be added to the queue and then run each time it’s called. 

Callback Pattern

A callback is simply a function passed into another function that invokes it at some point in the future. 

setTimeout(callback, timer)
Element.addEventListener(event, callback)
Array.map(function(item){...})

Let’s apply this to our bike use case to see how callbacks are implemented.

Bike.prototype.changeGearAsync = function(shiftObject, callback){
  let newIndex = shiftObject.currentIndex + shiftObject.changeBy;
  if (newIndex < 0 || newIndex > shiftObject.maxIndex) {
    callback(new Error("There is a problem"), null);
  } else {
    callback(null, newIndex);
  }
};

The argument callback is actually a function. If there’s an error, we invoke it and set the first argument with whatever error data we want to send back. On success, we null the error argument and pass back the good data.

Let’s invoke it

  // invoke async function with anonymous callback
  this.changeGearAsync(shiftObject, function(err, newIndex){
    if (err) {
      console.log("No Change");
    } else {
      that[shiftIndexName] = newIndex;
    }
  });

The callback pattern was widely accepted and used extensively, but it has some drawbacks. First, when several callbacks are chained together, they are nested one inside the other. This creates undue complexity, readability problems, and is difficult to reason about when reading someone else’s code. This flaw is known as callback hell. Callbacks also have no implicit error state (like try/catch does). It is up to the developer writing the callback to explicitly look for an error with an if condition. These obstacles led to the creation of promises. 

Arrow Functions

Arrow function syntax looks like this:

(arg1, arg2) => {...function body...}

Using an arrow function, we can remove the relic of older JavaScript  that = this bit and change the invocation of changeGearsAsync to the following.

  // the anonymous function is now an arrow function
this.changeGearAsync(shiftObject, (err, newIndex)=>{
  if (err) {
    console.log("No Change");
  } else {
    // we reference this instead of that
    this[shiftIndexName] = newIndex;
  }
});

Promises

Promises developed as libraries to handle asynchronous code in a way that made it easier to reason about when your code succeeded or failed. They also contain built-in mechanisms to chain one call after the other. Competing libraries eventually standardized in the browser as the Promise object

Bike.prototype.changeGearAsync = function(shiftObject){
  return new Promise(
    (resolve, reject) => {
      let newIndex = shiftObject.currentIndex + shiftObject.changeBy;
      if (newIndex < 0 || newIndex > shiftObject.maxIndex) {
        reject("New Index is Invalid: " + newIndex);
      } else {
        resolve(newIndex);
      }
    }
  );
};

First, the updated changeGearAsync function takes in the data we pass it and returns a new Promise object. We pass in a single argument: a callback function that itself has two functions passed to it, resolve and reject

When implementing a promise you perform whatever calculations, requests, and so on that you want in the callback function. Once done, if all’s right with the world, you invoke resolve with the data you want to pass back. If you encounter problems you signal that to the function caller by invoking reject along with any relevant errors as the argument. 

// invoke async function that returns a promise
this.changeGearAsync(shiftObject)
  .then(
    (newIndex) => {
      this[shiftIndexName] = newIndex;
      console.log(this.calculateGearRatio());
    }
  )
  .catch(
    (err) => {console.log("Error: " + err);}
  );

Now we have something a lot easier to reason about. If changeGearAsync works, the then function is invoked with the function passed into its argument. If it does not, catch is invoked. 

If the callback function itself returns an instance of Promise, that’s when things get exciting. You can simply chain those two promise functions together.

Bike.prototype.changeBothGears = function(frontChange, rearChange) {
  let shiftFront = {
    currentIndex: this.frontGearIndex,
    maxIndex: this.transmission.frontGearTeeth.length - 1,
    changeBy: frontChange
  };
  let shiftRear = {
    currentIndex: this.rearGearIndex,
    maxIndex: this.transmission.rearGearTeeth.length - 1,
    changeBy: rearChange
  };
  this.changeGearAsync(shiftFront)
    .then(
      (newIndex) => {
        this.frontGearIndex = newIndex;
        console.log(this.calculateGearRatio());
        return this.changeGearAsync(shiftRear);
      }
    )
    .then(
      (newIndex) => {
        this.rearGearIndex = newIndex;
        console.log(this.calculateGearRatio());
      }
    )
    .catch(
      (err) => {console.log("Error: " + err);}
    );
  };

Async/Await

The async and await operators. These build on promises, allowing them to be used in a way that much more closely resembles synchronous JavaScript.

The ES2016+ release introduced async functions and a different way of calling native promises. The structure of the promise remains the same, but what changes is how the promise is called. The call is wrapped in a function that uses the async and await keywords. What is returned is a promise object that contains either the resolved or rejected value.

function doSomething(msg){ 
  return new Promise((resolve, reject) => {
      setTimeout(
        () => {
          try {
            throw new Error('bad error');
            console.log(msg);
            resolve();
          } catch(e) {
            reject(e);
          }
        }, 
        1000);
    }) 
}

async function doSomethingManyTimes() {
  try {
    await doSomething("1st Call");
    await doSomething("2nd Call");
    await doSomething("3rd Call");
  } catch (e) {
    console.error(e.message);
  }
}
      
doSomethingManyTimes();  

Using .then method it would be this way

doSomething("1st Call")
  .then(() => doSomething("2nd Call"))
  .then(() => doSomething("3rd Call"))
  .catch(err => console.error(err.message));