đź“Ł Attention Salesforce Certified Trailblazers! Link your Trailhead and Webassessor accounts and maintain your credentials by December 14th. Learn more.
close
trailhead

Write Asynchronous JavaScript

Learning Objectives

After completing this unit, you’ll be able to:

  • Identify important asynchronous features in JavaScript.
  • Invoke functions asynchronously using setTimeout.
  • Write and invoke callback functions.
  • Write and invoke promise-based functions.
  • Describe asynchronous features in Lightning components.

Think way way back to when we first introduced the JavaScript engine. The engine has a single thread that does work, finishes, then has new work stuffed into it to start over again.

Of course, it’s critical that the thread not be blocked. 

Let’s see an example of this.

<html>
  <script>
    alert("Does JavaScript show first?");
  </script>
  <body>
    <p>
      Does HTML show first?
    </p>
  </body>
</html>

If you load this HTML page in your browser, you find that the alert pop up displays first and then blocks the display of the HTML. This is because the alert() function halts execution of the JavaScript thread until the user dismisses it. In short, when JavaScript blocks your browser, it is never a good user experience. 

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

Asynchronous JavaScript Is Everywhere

To begin the journey into asynchronicity, let’s revisit events and functions. Previously we looked at some HTML and JavaScript like this.

<!-- HTML -->
<button id="clicker">
//JavaScript
let button = document.getElementById("clicker");
button.addEventListener("click", handleClick);

In this example we added handleClick as an event handler to the click event emitted by the button. 

And there! We’ve already done some asynchronous JavaScript. 

When the 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. 

Now that we’ve seen some examples of asynchronous calls, we can look at some common asynchronous patterns and constructs. 

Callback Pattern

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

So in reality, we’ve already seen a bunch of callbacks. 

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. When you shift a bike’s gears, for the most part it works. But there’s still a small chance it can fail. This is an ideal scenario for asynchronous JavaScript. Let's see how it looks with a callback that take in the data about how the gears are to be shifted, then calls a passed in function when done. 

Bike.prototype.changeGearAsync = function(shiftObject, callback){
  let newIndex = shiftObject.currentIndex + shiftObject.changeBy;
  if (newIndex < 0 || newIndex > shiftObject.maxIndex) {
    callback(newIndex, null);
  } else {
    callback(null, newIndex);
  }
};

The argument callback is actually a function. If there’s an error, we invoke it setting 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. We can then see how our new gear change function is invoked. 

Bike.prototype.changeGear = function(frontOrRear, changeBy) {
  const shiftIndexName = frontOrRear + "GearIndex"
  const that = this;
  //contains state change for making the shift
  let shiftObject = {
    currentIndex: this[shiftIndexName],
    maxIndex: this.transmission[frontOrRear + "GearTeeth"].length,
    changeBy: changeBy
  }
  // 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 with try/catch). 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

In the previous example, you may have noticed this line.

const that = this;

This is a relic of older JavaScript. We snuck it in just to introduce a newer function syntax: arrow functions. Think back to what happens when a function is invoked. Specifically, that it binds to a new this context. Unlike other variables in scope of the closure of the anonymous function, JavaScript rebinds this when in fact we actually want the this of the containing function. 

A long standing workaround was to assign this to a new variable (by convention often called self or that) which would then keep the context reference in the closure.

Arrow functions remove this little bit of coding acrobatics, by not rebinding this. Arrow function syntax is like this.

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

Using an arrow function, we can remove the 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;
  }
});

Promising Stuff

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. Let’s morph bike one more time. 

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. 

Let’s see how we use this now. 

// 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 where things get exciting. You can simply chain those two promise functions together. For instance if we want to change both the front and rear gears. 

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);}
    );
  };

The changeBothGears function above shows us chaining two calls to changeGearsAsync, each with the object that corresponds to either the front or rear gears. After calling it the first time, we call it again at the end of the first then. Another then can be tacked onto that. Fundamentally, each time a then returns a promise, it can be followed with another then until we’ve exhausted all chained actions.  

Async/Await

Before signing off, it’s worth mentioning some newer additions to the asynchronous arsenal: the async and await operators. These build on promises, allowing them to be used in a way that much more closely resembles synchronous JavaScript. 

Lightning and Asynchronous JavaScript

Lightning Actions

Whenever anything needs to get done in Lightning components there’s an Action object. Actions contain details of what kind of action it is, what its parameters are, and request and response data. Actions are most commonly used as the API to surface Apex methods into a Lightning component. The code below demonstrates the use of an Apex class, bound to a component as the controller, then used in the helper by using the component.get() function. 

BikeLightningService.cls

// Apex Class BikeLightningService.cls defines a static method to be used in Lightning 
// and annotated with 
public with sharing class BikeLightningService {
  @AuraEnabled
  public static List<Bike_Gear__c> retrieveBikeGears(String bikeId){
    List<Bike_Gear__c> gears = new List<Bike_Gear__c>();
    if (bikeId != null && bikeId != ''){
      gears = [SELECT Id, Name, Gear_Type__c, 
                 (SELECT Id, Name, Number_of_Teeth__c FROM Bike_Gear_Teeth__r ORDER BY Number_of_Teeth__c) 
               FROM Bike_Gear__c 
               WHERE Bike__c = : bikeId ];
    }
    return gears;
  }
}

CalculateGearRatio.cmp

<!-- Lightning Component: CalculateGearRatio.cmp binds the apex class in the controller attribute -->
<aura:component controller="BikeLightningService" implements="flexipage:availableForRecordHome, force:hasRecordId">
    <aura:attribute name="recordId" type="String"/>
...

CalculateGearRatioHelper.js

// CalculateGearRatioHelper.js uses the Apex method, surfaced in JavaScript 
// and set into the action variable via component.get(). The callback is 
// designated in the setCallback() call. 
getGears : function(recordId, component) {
  // here we get a local handle to the surfaced Apex method
  const action = component.get("c.retrieveBikeGears");
  action.setParam('bikeId', recordId);
  // here is an anonymous function set as the callback
  // note use of arrow functions as callbacks throughout
  // even in standard callbacks with map and forEach functions
  action.setCallback(this, (response)=>{
    const state = response.getState();
    if (state === "SUCCESS") {
      const gearsForBike = response.getReturnValue();
      let frontGears, rearGears;
      gearsForBike.forEach((item)=>{
        if(item.Gear_Type__c === "Front (Drive)") {
          frontGears = item.Bike_Gear_Teeth__r;
        } else if (item.Gear_Type__c === "Rear (Driven)") {
          rearGears = item.Bike_Gear_Teeth__r;
        }
      });
    const frontList = frontGears.map((item, index)=>{
      return {index: index, value: Number(item.Name)};
    });
    const rearList = rearGears.map((item, index)=>{
      return {index: index, value: Number(item.Name)};
    });
    component.set("v.frontGears", frontList);
    component.set("v.rearGears", rearList);
    this.setGearRatio(component);
}
});
  // this tells the framework this action is ready to be
  // queued up to take a trip to the server
  $A.enqueueAction(action);
} 

When an Apex method needs to pass data back to your component, you explicitly register the callback via the Action.setCallback function. This is commonly done with an anonymous function. 

Lightning Events

Of course, the use of an aura:event is also asynchronous. Here, the function to be invoked is completely decoupled from the event that initiates it. The aura:handler tag identifies each event a given component is listening for. This tag wires up the event to a local event handler. You can then decide what to do in that component when the external event fires. 

Promises and the Lightning Console API

While Lightning components have their own implementation of asynchronous actions, Lightning console apps have an extensive JavaScript API that makes heavy use of promises. A firm grasp of invoking promises, and how to wrap those in a Lightning component are very helpful. Fortunately, the Lightning component developer guide has examples on how to do just that. 

One critical piece of making this work is the use of the $A.getCallback function. This takes a normal callback and wraps in an object that the Lightning event framework is aware of. 

Becoming one with asynchronous patterns is key to creating modular functionality in JavaScript and the Lightning component framework. Callbacks, and promises can help you on your way to being a Lightning component developer.

Resources 

Code Sample Project for This Module

Using Promises in Lightning Components

JavaScript Arrow Function Expressions

JavaScript Promises

JavaScript Async keyword

JavaScript Await keyword

Lightning Component Specialist Superbadge

retargeting