Exploring Asynchronous JavaScript and Best Practices

·

4 min read

By default JavaScript is synchronous. The JavaScript engine maintains a stack data structure called the call stack to keep track of function execution. And, as stacks go, the last item added to the stack is the first item to be manipulated.

When the JavaScript engine invokes a function, it adds it to the stack, and the execution starts. If the currently executed function calls another function, the engine adds the second function to the stack and starts executing it. Once it finishes executing the second function, the engine takes it out from the stack. The control goes back to resume the execution of the first function from the point it left it the last time. Once the execution of the first function is over, the engine takes it out of the stack. This continues until there is nothing left to put into the stack.

console.log("3");
console.log("2");
console.log("1");
console.log("Blast off!");

In the snippet above, each line executes in order. 2 will only log to the console after 3 has been logged and so on.

Defining Asynchronous Execution

Asynchronous execution in JavaScript allows a unit of work to run separately from the primary application sequence. This means that code can execute out of sequence or order, providing more flexibility. An example using setTimeout demonstrates asynchronous behavior:

console.log("3");
console.log("2");
setTimeout(() => {
    console.log("1");
},1000)
console.log("Blast off!");

Here, the setTimeout callback function waits for one second before executing, resulting in non-sequential logging. For this reason, the output will not log in sequence as expected but rather as:

3
2
Blast off!
1

Why Use Asynchronous Code?

There are situations where synchronous execution falls short and where asynchronous execution comes to the rescue. Asynchronous operations fall into two categories: those involving Browser API and Web API (utilizing callback functions) and those using Promises, which offer improved handling of asynchronous tasks.

  1. Browser API and Web API (uses callback function)
  • methods like setTimeout()

  • event handlers like onClick()

  1. Promises

Browser APIs like setTimeout and event handlers rely on callback functions. A callback function executes when an asynchronous operation completes.

In other words, event handlers do not follow the code sequence in order. The event handler is provided and will be called only when the event occurs (i.e. user clicks a button).

As for promises, the easiest way, in my opinion, to deal with them is to use async/await.

Promises and Async/Await

Promises offer a cleaner approach to handling asynchronous tasks, and ES8 introduced async/await to simplify working with promises. An asynchronous function consists of two main keywords: async and await. The async keyword makes the function asynchronous, always returning a promise, while await signals the execution to wait for a defined task.

const fetchPokemon = async (id) => {
  const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
  const data = await res.json()
  console.log(data);
};

fetchPokemon("pikachu");
  • Line 2: waits for the response from the fetch request to the pokemon API.

  • Line 3: waits for the response to be converted into json.

  • Line 4: this converted data is then logged to the console when the fetchPokemon() function is invoked on line 7.

This looks great!

But what happens in the case where we do not pass an argument into the function like so: fetchPokemon()? This would throw an error but with our current implementation, we have no way to handle errors. To resolve this issue, two more keywords are introduced: try and catch.

try consists of the code we want to be executed and catch consists of error handling in the event the execution of our code fails.

const fetchPokemon = async (id) => {
  try {
    const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.log(err);
  }
};

fetchPokemon("pikachu");

So now, if the function is ran with no id argument, the error is handled gracefully as its logged into the console.

Conclusion and Next Steps

To become proficient in writing asynchronous code, practice is essential. Utilize code sandboxes or create new projects to experiment with asynchronous JavaScript and gain a deeper understanding of its nuances and best practices.