JavaScript Async/Await

Dr. Greg Bernstein

October 8th, 2019

Async/Await

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.js in NodeRequests.zip

const rp = require('request-promise-native');
let site1 = {
    uri: 'https://www.grotto-networking.com',
    method: 'HEAD', // What does this do?
    resolveWithFullResponse: true
};

let site2 = {
    uri: 'http://www.google.com',
    method: 'HEAD',
    resolveWithFullResponse: true
};

let site3 = {
    uri: 'http://www.kiteboardingcairns.com.au/kitesurfing-australia/',
    method: 'HEAD',
    resolveWithFullResponse: true
};

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

Web requests in order async/await

From threeInOrderAsync.js in NodeRequests.zip

const rp = require('request-promise-native');
let site1 = {
    uri: 'https://www.grotto-networking.com',
    method: 'HEAD', // What does this do?
    resolveWithFullResponse: true
};

let site2 = {
    uri: 'http://www.google.com',
    method: 'HEAD',
    resolveWithFullResponse: true
};

let site3 = {
    uri: 'http://www.kiteboardingcairns.com.au/kitesurfing-australia/',
    method: 'HEAD',
    resolveWithFullResponse: true
};

let start = new Date();
async function inOrder() {
    let res = await rp(site1);
    let time = (new Date() - start)/1000;
    console.log(`Grotto status: ${res.statusCode}, time: ${time}`);
    res = await rp(site2);
    time = (new Date() - start)/1000;
    console.log(`Google status: ${res.statusCode}, time: ${time}`);
    res = await rp(site3);
    time = (new Date() - start)/1000;
    console.log(`Aus kiteboarding status: ${res.statusCode}, 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.

// File sailingCountRecursive.js in NodeRequests.zip
const rp = require('request-promise-native');
var totalCount = 0;
var year = 2019;
var desiredCount = 150;

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

let site1 = {
    uri: 'https://windsurf.grotto-networking.com/data/logs/windEvents2019.json',
    json: true
};

rp(site1).then(logAdd.bind(null, year));

Challenge

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

async/await version

File sailingCount.js in NodeRequests.zip

const rp = require('request-promise-native');
let year = 2019; // Start here and work backwards
let desiredCount = 150; // How many years do we have to go back to reach this

function logSize(data) { // Simple transform of the data
    return (data.length);
}

async function getThem(year) {
    let totalCount = 0;
    let curYear = year;
    while (totalCount < desiredCount) {
        let site = {
            uri: 'https://windsurf.grotto-networking.com/data/logs/windEvents' + curYear + '.json',
            json: true
        };
        totalCount += await rp(site).then(logSize);
        console.log(curYear, totalCount);
        curYear -= 1;
    }
}

getThem(2019);

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.