CSE 154

Lecture 11: Async/Await

Agenda

Async/Await

More Promise Examples (now with Async/Await!)

Reminders and Administrivia

Quiz 2 will be released at 3 pm today (due tomorrow by 3 pm PST)

Async/Await

Let's Review Promises

Analogy: You're out for pizza

At the restaurant you might follow these steps:

  • Request menu
  • Order pizza
  • Check pizza
  • Eat pizza
  • Pay for pizza

Each step can’t continue before the previous finishes.

Callback (again) to Callbacks:

We can imagine all of these steps as a series of callbacks, depending on the event previous to them:

menu.addEventListener('click', function() {
  console.log("making pizza");
  setTimeout(function() {
    console.log("pizza made");
    pizza.classList.remove("inactive");
    pizza.addEventListener('click', function() {
      console.log("eating pizza");
      setTimeout(function() {
        console.log("pizza eaten");
        bill.classList.remove("inactive");
        bill.addEventListener('click', function() {
          console.log("paying bill");
          setTimeout(function() {
            console.log("all done");
          }, 3000);
        });
      }, 2000);
    });
  }, 5000);
});

JS

Promises to the Rescue

We can make the previous code read like this

orderPizza()
  .then(eat)
  .then(pay)
  .catch(badPizza);

Ok, creating the promises and executor functions was a tad messy

But the end result is that we can follow what's going on here!

Promises

Three states of a Promise: pending, resolved, rejected

Promises are a sort of contract:

  • Something will happen
  • You can have multiple things happen.
  • And catch any errors.

Can only go from Pending to Fulfilled or Rejected (no takebacks)

Example: "I promise to return to your table"

  • Pending: Waiting for my pizza
  • Fulfilled: Pizza has arrived!!
  • Rejected: Kitchen ran out of cheese. :(

Promises on MDN

Creating a Promise

function description
new Promise(executorFn) Creates a new Promise object with the executorFn
promiseObject.then(onFulfilled, onRejected) Invokes the onFulfilled (onRejected) function when the promise is fulfilled (rejected)
promiseObject.catch(callback) Invokes the callback function if the promise is rejected (or an error occurs)

function executorFn(resolve, reject) {
    ...
    if (conditionMet) {
        resolve(); // Passed by the Promise object
    } else {
        reject(); // Passed by the Promise object
    }
}
  

JS

You define this function and pass it into the Promise constructor

Back to that Pizza

We can pass a value to resolve...


function orderExecutor(resolve, reject) {
    console.log('making our pizza...');
    setTimeout(function() {
        resolve("Here's your pizza!");
    }, 5000);
}

let orderPizza = new Promise(orderExecutor); 
orderPizza.then(function (value) { console.log(value); });
      

JS

That value gets passed into then

Back to that Pizza

The functions passed to then can pass values to the next then callback


function eat(value) { 
    return value + ", and now it's gone";
}

let orderPizza = new Promise(orderExecutor); 
orderPizza.then(eat).then(function (value) { console.log(value); });
      

JS

Back to that Pizza

You can also return other promises, which halt the execution of the next then callback until it's resolved


function eatExecutor(resolve, reject) {
    console.log('eating our pizza...');
    setTimeout(resolve, 3000);
}

function eat() { 
    return new Promise(eatExecutor);
}

let orderPizza = new Promise(orderExecutor); 
orderPizza.then(eat).then(function () { console.log('Paying the bill!'); });
      

JS

Still Asynchronous


function orderExecutor(resolve, reject) {
    console.log('Pizza ordered...');
    resolve("Here's your pizza!");
}

let orderPizza = new Promise(orderExecutor); 
orderPizza.then(function (value) { console.log(value); });
console.log("Waiting for my pizza!");
      

JS

In what order do these log statements appear in the console?

Rejecting a Pizza


function orderExecutor(resolve, reject) { // MUST have both parameters defined
    console.log('Pizza ordered...');
    setTimeout(function() {
        reject("Ran outta cheeese. Can you believe it?");
    }, 2000);
}

let orderPizza = new Promise(orderExecutor); 
orderPizza
    .then(function () { console.log("Woohoo, let's eat!"); })
    .catch(function (value) { console.log(value); });
      

JS

then and catch Return Promises


function executor(resolve) {
    resolve('Woohoo!');
}

let myPromise = new Promise(executor); 
let thenPromise = myPromise.then(console.log);
let catchPromise = thenPromise.catch(console.error);

console.log(thenPromise instanceof Promise); // true
console.log(catchPromise instanceof Promise); // true

console.log(myPromise === thenPromise); // false
console.log(myPromise === catchPromise); // false

console.log(thenPromise === catchPromise); // false
            

JS

then and catch return new Promises

then and catch Return Promises


function executor(resolve) {
    resolve('Woohoo!');
}

function processStr(val) {
    // mellow out that message a bit
    return val.toLowerCase().replace('!', '');
}

let myPromise = new Promise(executor); 
let thenPromise = myPromise.then(processStr);
console.log(thenPromise);
            

JS

processStr returns a string, but then turns it into a Promise that immediately resolves with the value "woohoo"

then and catch Return Promises

The previous slide is equivalent to the below:


function executor(resolve) {
    resolve('Woohoo!');
}

function processStr(val) {
    // mellow out that message a bit
    return new Promise(function(resolve) {
        resolve(val.toLowerCase().replace('!', ''));
    });
}

let myPromise = new Promise(executor); 
let thenPromise = myPromise.then(processStr);
console.log(thenPromise);
            

JS

then and catch Return Promises


function executor(resolve) {
    resolve('Woohoo!');
}

function processStr(val) {
    // mellow out that message a bit
    return new Promise(function(resolve) {
        setTimeout(function() { 
            resolve(val.toLowerCase().replace('!', ''));
        }, 5000);
    });
}

let myPromise = new Promise(executor); 
let thenPromise = myPromise.then(processStr);
console.log(thenPromise);
            

JS

Now, thenPromise is "PENDING" and won't resolve until the promise returned by processStr resolves.

Promises to the Rescue

This chaining of promises is what makes the below possible

orderPizza()
  .then(eat)
  .then(pay)
  .catch(badPizza);

Async/Await

What if?


let myBtn = qs('button:nth-child(1)');
while (!myBtn.clicked) {
  // twiddle our thumbs
}
console.log('"Finally Been Clicked", starring Drew Barrymore');

let myBtn2 = qs('button:nth-child(2)');
while (!myBtn2.clicked) {
  // twiddle our thumbs
}
console.log('"Click 2", never coming soon to a theater near you');
            

JS

What if? (with Promises)


function firstBtnClick() {
    return new Promise(function (resolve) {
        let myBtn = qs('button:nth-child(1)');
        myBtn.addEventListener('click', resolve);
    }); 
}

function nextBtnClick() {
    return new Promise(function (resolve) {
        let myBtn = qs('button:nth-child(2)');
        myBtn.addEventListener('click', resolve);
    }); 
}

firstBtnClick()
    .then(() => { console.log('"Finally Been Clicked", starring Drew Barrymore'); })
    .then(nextBtnClick)
    .then(() => { console.log('"Click 2", never coming soon to a theater near you'); });
            

JS

What if? (with Promises Plus a Little Syntactic Sugar)


function firstBtnClick() {
    return new Promise(function (resolve) {
        let myBtn = qs('button:nth-child(1)');
        myBtn.addEventListener('click', resolve);
    }); 
}

function nextBtnClick() {
    return new Promise(function (resolve) {
        let myBtn = qs('button:nth-child(2)');
        myBtn.addEventListener('click', resolve);
    }); 
}

await firstBtnClick();
console.log('"Finally Been Clicked", starring Drew Barrymore');
await nextBtnClick();
console.log('"Click 2", never coming soon to a theater near you');
            

JS

Mind. Blown.


await firstBtnClick();
console.log('"Finally Been Clicked", starring Drew Barrymore');
await nextBtnClick();
console.log('"Click 2", never coming soon to a theater near you');
            

JS

mind blown bitmoji

Async/Await

"Syntactic sugar" that wraps a function's return in a promise

Allows code to "wait" for the thing to return.

async function sayHelloAsync(name) {
  return "Hello " + name;
}

console.log(sayHelloAsync("dubs")); // Promise
let message = await sayHelloAsync("dubs");
console.log(message); // "Hello dubs"

JS

  • async does the same thing to functions that then does
    • It wraps the return value in a Promise whose resolved value is the return value
  • await halts execution of the code until the Promise is resolved and then returns the resolved value of the promise

Back to that Pizza


function orderExecutor(resolve, reject) { // reject not required here 
    console.log('making our pizza...');
    setTimeout(resolve, 5000);
}

await new Promise(orderExecutor); 
console.log('eating pizza!');
      

JS

Back to that Pizza

We can pass a value to resolve...


function orderExecutor(resolve, reject) {
    console.log('making our pizza...');
    setTimeout(function() {
        resolve("Here's your pizza!");
    }, 5000);
}

let pizza = await new Promise(orderExecutor); 
console.log(pizza);
      

JS

That value gets passed to the function passed into await

Back to that Pizza


async function eat(value) { 
    return value + ", and now it's gone";
}

let pizza = await new Promise(orderExecutor); 
let eatingResult = await eat(pizza);
console.log(eatingResult);
      

JS

Back to that Pizza

You can also return other promises, which halt the execution of the next then callback until it's resolved


function eatExecutor(resolve, reject) {
    console.log('eating our pizza...');
    setTimeout(resolve, 3000);
}

async function eat() { // don't need async here...why not?
    return new Promise(eatExecutor);
}

let pizza = await new Promise(orderExecutor); 
let eatingResult = await eat(pizza); 
console.log('Paying the bill!');
      

JS

Still Asynchronous...or is It?


function orderExecutor(resolve, reject) {
    console.log('Pizza ordered...');
    setTimeout(function() {
        resolve("Here's your pizza!");
    }, 3000);
}

let pizza = await new Promise(orderExecutor); 
console.log(pizza);
console.log('Waiting around');
      

JS

In what order do these log statements appear in the console?

Rejecting a Pizza


function orderExecutor(resolve, reject) { // MUST have both parameters defined
    console.log('Pizza ordered...');
    setTimeout(function() {
        reject("Ran outta cheeese. Can you believe it?");
    }, 2000);
}

try {
    let pizza = await new Promise(orderExecutor); 
    console.log("Woohoo, let's eat!");
} catch (error) {
    console.log(error);
}
      

JS

For error-handling with async/await, you must use try/catch instead of .then/.catch

The catch statement will catch any errors that occur in the then block (whether it’s in a Promise or a syntax error in the function), similar to the .catch in a fetch promise chain

Error-handling with async/await

For error-handling with async/await, you must use try/catch instead of .then/.catch

The catch statement will catch any errors that occur in the then block (whether it’s in a Promise or a syntax error in the function), similar to the .catch in a fetch promise chain

When Do I Need the Keyword async?

For any function that is await'd, but that doesn't return a promise (although it'll still work to add async even if it does)


async function eat(value) { 
    return value + ", and now it's gone";
}

let pizza = await new Promise(orderExecutor); 
let eatingResult = await eat(pizza); // don't need to do this. Why not?
console.log(eatingResult);
      

JS

For any function that that uses await in its implementation


async function orderPizza() {
    let pizza = await new Promise(orderExecutor); 
    return 'Done!';
}

console.log(await orderPizza());
console.log('What now?');
      

JS

Example: Let's Fix that Pizzeria

Pizzeria: pizza.html

Remind Me, Why Do I Care About Promises?

Uncertainty

Some operations take an unknown amount of time or have a not insignificant chance of failure

  • File I/O
  • Database transactions
  • HTTP requests
    • Resource doesn't exist (404)
    • Really long response time or server is down
    • Bad internet connection

Whether these operations succeed or fail, we still want to do something in response

Remember, Promises guarantee a response (fulfilled/resolved or rejected)

Next Week: fetch

Allows us to use JavaScript to request resources from servers connected to the internet


fetch("http://www.weather.com")
      

Can think of it as replicating the behavior of our browser address bar

fetch returns a Promise!

Next Week: fetch

Allows us to use JavaScript to request resources from servers connected to the internet


fetch("http://www.weather.com")
    .then(checkStatus) // check out HTTP status code
    .then(getText) // get the content from the response
    .then(process) // do something with the response content
    .catch(handleError); // do something on failure
      

JS

Or


try {
    let resp = await fetch("http://www.weather.com");
    checkStatus(resp); // check out HTTP status code
    let text = await getText(resp); // get the content from the response
    process(text) // do something with the response content
} catch (error) {
    handleError(error);
}
      

JS

Example: Fetching HTML

Get HTML: html.html

More questions from this lecture?

Especially if you're watching a recording, write your question on PollEverywhere and I'll answer them at the start of next lecture.

Either on your phone or computer, go to PollEv.com/robcse