Programming with APIs

9.2 Asynchronous Code

# Async vs Sync

Almost all the code that we have written to this point has been synchronous code. That means that you write the code in the order that you expect it to run. The first line of code inside your function will run and complete before the second line of code.

However, there is also Asynchronous code in JavaScript. Promises are an example of this.

When you write code that will take an unknown amount of time to complete but you don't want your App to freeze and do nothing until that one task is complete, then you need to use Async tasks.

JavaScript has a mechanism known as the event loop. When you pass a list of commands to the event loop, it will keep running each of those commands in order until it gets to the end of your list. This list is known as the main stack. JavaScript keeps trying to run commands as long as there is something on the main stack.

However, sometimes there is a command that would stop the event loop from getting to the next command. It gets blocked from going to the next command in the stack. These types of commands are things that take a long time to complete, like talking to a database, or the file system, or a network request for a file, or a timer that needs to wait before running.

There are specific tasks that are going to take too long and they get put into a secondary area so they can wait for their result. These tasks are known as asynchronous.

If the code stays on the main stack and insists on being run before the event loop moves on to the next item then it is called synchronous.

# The JavaScript Loop

The JavaScript engines, like Chrome's V8, use a single thread to process all your code. It builds a stack of commands to run, in the order that you have written them. As long as each command belongs to the CORE JavaScript then it gets added to the main run stack.

When something that belongs to the Web APIs is encountered it gets added to a secondary list of tasks. Each time the main stack is finished then the JavaScript engine turns it attention to the secondary list of tasks and will try to run those.

The video below does a great job explaining all the parts of this process. It is about 25 minutes so give yourself some time to watch it.

You don't need this information to do any of the assignments this semester. However, it will help you to avoid errors in your code and to better understand how your code will be interpreted by the JavaScript engine. This will lead to you writing much better code in the long run.

And more about the event loop, asynchronous features, timers, tasks, and microtasks...

# Tasks, MicroTasks, UI Render Tasks

Just as was explained in the Jake Archibald video In the Loop (opens new window), there are different types of tasks that happen at different points on the event loop. Understanding this sequence will help you to avoid and solve strange errors in your code.

The main stack is all the code that runs on the event loop. JavaScript wants to run all the commands in the main execution context before it does anything with the tasks or microtasks.

The UI render tasks are the one exception to leaving the event loop. The browser can decide to exit the loop and run updates (calculate and paint) for the UI. UI render tasks are mainly automatic things like updating the interface based on a CSS transition or animation. However, you can create code that runs during this phase with requestAnimationFrame().

If you create a task and inside that task you create another task, the second one will not run until the first task is completed and the event loop has looped around again.

If you create a microtask and inside that you create another microtask, then the browser will also run the subsequent microtask(s) before returning to the event loop.

# Timers

In JavaScript we can set timers to call a function at some point in the future.

We use the setTimeout() or setInterval functions and pass them a callback function plus a minimum time delay to use (written in milliseconds). We say minimum time delay because the main stack could still have code in it that is being run. The function from the timeout has to wait for that other code to finish before it can be called.

function f1() {
  console.log('f1 is running');
}
function f2(name) {
  console.log(`Hello ${name}. f2 is running.`);
}

setTimeout(f1, 1000);
setTimeout(f2, 1000, 'Dean'); //Dean gets passed to f2 as the argument
1
2
3
4
5
6
7
8
9

# Timeouts

When you want to run a function after a specific time delay then setTimeout is the built-in function to accomplish this.

setTimeout(myFunc, 3000); //call the function myFunc after at least 3000ms

setTimeout(function () {
  alert('hi');
}, 2500); //run the anonymous function after 2500ms

setTimeout('myFunc()', 1000); //call the function myFunc after 1 second

window.setTimeout(myFunc, 3000); //same as first example
1
2
3
4
5
6
7
8
9

# Intervals

When you want to run something repeatedly after a set time, for example, once every ten seconds, then setInterval is the function to call.

Timers running on intervals can be stopped if you call the clearInterval( ) method. Just make sure that you keep a reference to the interval.

let intervalId = setInterval( myFunc, 3000);
//keep calling myFunc every 3 seconds
....
//use the intervalId to stop the interval calls running
clearInterval( intervalId );
1
2
3
4
5

# Recursive Timeouts

Another way to approach timed intervals is with a recursive call to the setTimeout function. Inside the function that runs following your setTimeout, you make another call to the same function. The process repeats itself.

The difference with the recursive calls is that it allows us to change the amount of time between the calls or stop it after any call.

We can use this method to create ever shorter time spans between callbacks or random times between callbacks.

# Async - Await

One of the features added in ES6 was async and await. The combination of these keywords lets us use asynchronous features like fetch inside a function but write the code in a synchronous style.

Start by making a function into an async one.

//give a function the ability to pause and wait for a promise
async function f1() {
  //this function declaration can now pause and wait for a promise to complete
}

let f2 = async function () {
  //this function expression can pause and wait for a promise to complete
};

let res = async () => {
  //this arrow function can also pause and wait
};
1
2
3
4
5
6
7
8
9
10
11
12

Then you can use the await keyword as many times as you want. Each one is capable of pausing the function to wait for an async result from a promise.

//make a function that can be called to create a delay
function delay(len) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(true);
      //call the resolve method after `len` milliseconds have passed
    }, len);
  });
}

//function ready to be paused
let res = async () => {
  //this first call will run 1 second in the future
  let isReady = delay(1000);
  console.log(isReady); // `pending Promise` at this point. Not resolved yet.

  //try again with await
  //now pause the function and wait for a result
  let isReadyNow = await delay(1000); //function actually pauses to wait for the result from delay()
  console.log(isReadyNow); //`true`
  //isReadyNow actually has a value this time because of await.
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Starting next week, we will be making calls to remote APIs and retrieving dynamic data. At that point we can use async await to pause a function and wait for the result if we want.

# What to do this week

TODO Things to do before next week.

  • Read all the content from Modules 9.1, 9.2, and 10.1.
  • Continue working on the Hybrid Exercises
Last Updated: 5/31/2023, 8:15:38 PM