JavaScript Async/Await

Dr. Greg Bernstein

Updated October 19th, 2021

Async/Await

Learning Objectives

  • Understand and use asynchronous functions and the await keyword in JavaScript
  • Understand how “async/await” relate to JavaScript Promises
  • Use “async/await” style programming to simplify ordered asynchronous operations

Readings/References

async function declaration

From MDN async function

An asynchronous function is a function which operates asynchronously via the event loop, using an implicit Promise to return its result.

Example:

function square(x){return x*x}

async function asquare(x){return x*x}

Function usage

Using async functions:

// regular function returns a value
console.log(square(8));
// async function returns a promise
asquare(8).then(y => console.log(y));

Why bother?

await operator

From MDN await

The await operator is used to wait for a Promise. It can only be used inside an async function.

  • Syntax: [rv] = await expression;
  • Where expression is a Promise or a plain value, and rv is set to the value returned by the Promise on success.

await Example 1


function square(x){return x*x}
async function asquare(x){return x*x}

async function test() {
  p1 = new Promise(function(resolve, reject){
      setTimeout(()=>resolve("Hi Class!"), 1000);
  });
  let x = await square(9); // wait for a value
  let y = await asquare(10); // wait for async fuction
  console.log(`x= ${x}, y=${y}, at ${new Date()}`);
  let msg = await p1; // wait for a promise
  console.log(`message = ${msg}, at ${new Date()}`);
}

console.log(`Starting test at ${new Date()}`);
test();
console.log(`Finished calling test`);

await Example 1 Continued

Key takeaways:

  1. Inside an async function we can use await to wait for promises to resolve and get their value.

  2. test() itself has to be an async function so we can use await in it.

  3. test() returns a promise and hence is run via the event loop.

  4. Ordered time consuming operations are much easier to think about and program with async/await.

What about rejection?


function square(x){return x*x}
async function asquare(x){return x*x}

async function test() {
  p1 = new Promise(function(resolve, reject){
      setTimeout(()=>reject("Surprise Class!"), 2000);
  });
  let x = await square(9); // wait for a value
  let y = await asquare(10); // wait for async fuction
  console.log(`x= ${x}, y=${y}, at ${new Date()}`);
  let msg = await p1; // wait for a promise
  console.log(`message = ${msg}, at ${new Date()}`);
}

console.log(`Starting reject test at ${new Date()}`);
test();
console.log(`Finished calling reject test`);

rejectionexception!


function square(x){return x*x}
async function asquare(x){return x*x}

async function test() {
  try {
    p1 = new Promise(function(resolve, reject){
        setTimeout(()=>reject("Surprise Class!"), 2000);
    });
    let x = await square(9); // wait for a value
    let y = await asquare(10); // wait for async fuction
    console.log(`x= ${x}, y=${y}, at ${new Date()}`);
    let msg = await p1; // wait for a promise
    console.log(`message = ${msg}, at ${new Date()}`);
  } catch (e) {
    console.log(`exception: ${e}`);
  }
}

console.log(`Starting reject test 2 at ${new Date()}`);
test();
console.log(`Finished calling reject test 2`);

async/await exception handling

Inside async functions when we use await we can deal with Promise rejection via the usual try/catch exception handling mechanisms!

More async/await Examples

Silly Clock with Promises

Let’s try throwing around a promise goodTimePromise.js

myTime = 0.0;
startTime = new Date();

function oneSecond() { // Returns a promise that resolves in one second
    return new Promise(function(resolve, reject){
        setTimeout(()=>resolve(), 1000);
        });
}

function advanceTime() {
    myTime += 1.0;
    elapsedTime = (new Date() - startTime)/1000.0;
    console.log(`myTime = ${myTime}, elapsedTime = ${elapsedTime}`);
    return oneSecond(); // Returns another new one second promise
}
// What will this do?
oneSecond()
    .then(advanceTime)
    .then(advanceTime)
    .then(advanceTime);

Silly Clock async/await

File: goodTimeAsync.js in the AsyncExamples.zip

let myTime = 0.0;
let startTime = new Date();

function oneSecond() {
    // Returns a promise that resolves in one second
    return new Promise(function(resolve, reject) {
        setTimeout(() => resolve(), 1000);
    });
}

function advanceTime() {
    myTime += 1.0;
    elapsedTime = (new Date() - startTime) / 1000.0;
    console.log(`myTime = ${myTime}, elapsedTime = ${elapsedTime}`);
}

async function myTimer(n) {
    for (let i = 0; i < n; i++) {
        await oneSecond();
        advanceTime()
    }
}

myTimer(5); // Set number to count to whatever you like.

Web requests in order promises

File: threeInOrder.mjs in NodeRequests.zip

import fetch from 'node-fetch';
let site1 = {
  url: "https://www.grotto-networking.com",
  options: {method: "HEAD"}
};

let site2 = {
  url: "http://www.google.com",
  options: {method: "HEAD"}
};

let site3 = {
  url: "https://kitewest.com.au/",
  options: {method: "HEAD"}
};

let start = new Date();
fetch(site1.url, site1.options)
  .then(res => {
    // console.log(`Grotto status: ${JSON.stringify(res)}`);
    let time = (new Date() - start) / 1000;
    console.log(`Grotto status: ${res.statusText}, time: ${time}`);
    return fetch(site2.url, site2.options);
  })
  .then(res => {
    let time = (new Date() - start) / 1000;
    console.log(`Google status: ${res.statusText}, time: ${time}`);
    return fetch(site3.url, site3.options);
  })
  .then(res => {
    let time = (new Date() - start) / 1000;
    console.log(`Aus kiteboarding status: ${res.statusText}, time: ${time}`);
  });
console.log("Starting my web requests:");

Web requests in order async/await

From threeInOrderAsync.mjs in NodeRequests.zip

/*  Demonstration of promises to put HTTP requests for
    Node.js in a particular order with async and await!
*/
import fetch from 'node-fetch';
let site1 = {
    url: "https://www.grotto-networking.com",
    options: { method: "HEAD" }
};

let site2 = {
    url: "http://www.google.com",
    options: { method: "HEAD" }
};

let site3 = {
    url: "https://kitewest.com.au/",
    options: { method: "HEAD" }
};

let start = new Date();
async function inOrder() {
    let res = await fetch(site1.url, site1.options);
    let time = (new Date() - start) / 1000;
    console.log(`Grotto status: ${res.statusText}, time: ${time}`);
    res = await fetch(site2.url, site2.options);
    time = (new Date() - start) / 1000;
    console.log(`Google status: ${res.statusText}, time: ${time}`);
    res = await fetch(site3.url, site3.options);
    time = (new Date() - start) / 1000;
    console.log(`Aus kiteboarding status: ${res.statusText}, time: ${time}`);
}
console.log("Starting my web requests:");
inOrder();

Undetermined Amount of Data Collection

  • Here we are collecting sailing logs from the web such as: https://windsurf.grotto-networking.com/data/logs/windEvents2019.json. There are ten years of logs from 2009 to 2019.

  • Each log has between 40 and 80 samples (sailing sessions)

  • We want to start with the current year and go back in time till we have enough samples to do some statistics. We don’t know how many files we will need to recover ahead of time.

Promises Based

Uses recursion and side effects. It is also hard to read. SailingCountRecursive.mjs

import fetch from 'node-fetch';

var totalCount = 0;
var year = 2021;
var desiredCount = 200;

function logAdd(year, data) {
    console.log(`Year ${year}, sailing sessions = ${data.length}`);
    totalCount += data.length;
    if (totalCount < desiredCount) {
        let url = 'https://windsurf.grotto-networking.com/data/logs/windEvents' + (year - 1) + '.json';
        fetch(url).then(res => res.json()).then(logAdd.bind(null, year - 1));
    }
}

let url1 = 'https://windsurf.grotto-networking.com/data/logs/windEvents' + year + '.json';

fetch(url1).then(res => res.json()).then(logAdd.bind(null, year));

Challenge

Come up with a clean non-recursive Promise based implementation!

async/await version

File sailingCount.mjs in NodeRequests.zip

/*  Demonstration of Promises and async/await to
    visit an indeterminant number of data sources one at a time
    until a criteria is reached.
*/
import fetch = from 'node-fetch';
let desiredCount = 150; // How many years do we have to go back to reach this

async function getThem(year) {
    let totalCount = 0;
    let curYear = year; // Start here and work backwards
    while (totalCount < desiredCount) {
        let url = 'https://windsurf.grotto-networking.com/data/logs/windEvents' + curYear + '.json';
        totalCount += await fetch(url)
            .then(res => res.json())
            .then(data => data.length);
        console.log(curYear, totalCount);
        curYear -= 1;
    }
}

getThem(2021);

Summary

  • async/await doesn’t replace Promises, but uses Promises

  • async/await much easier for in-order type operations, these come up both on the server-side and the client-side.

  • We will still want to use Promises directly particularly in very simple cases and in cases that require parallel operations such as Promise.all() and Promise.race()

  • Remember that Promises and async/await are mechanisms for asynchronous programing provided by JavaScript. Other languages and environments may have more or less mechanisms to support asynchronous operations.

// reveal.js plugins