Promise, generators, async/await



Promise, generators, async/await

2 1


async

Workshop on implementing async-await

On Github JSWorkshops / async

Promise, generators, async/await

Problem

Async programming in JS is hard.

  • The web platform is a event driven environment.
  • We need to deal with all sorts of events.
  • Mouse events, key events, network events, etc.
  • And we are constantly trying to react to these events.
  • Right now, we have to chain callbacks together to handle events. This is hard today...
/* Pyramid of DOOM! */
try{
  var url = "someU.data";
  showIndicator("fadein",
    downloadData(url, function(data){
       processData(rawData, function(data){
          displayData(data, removeIndicator)
     });
    });
  });
}catch(e){
  console.log("argh... screw this!");
  throw e;
}
  • We commonly end up writing code like the above to handle different actions.
  • Dictated by the user experience we want to create.
  • As well as coping with errors - which I didn't do above
  • It gets really complicated. And it's really hard to reason about.

Part 1 - Promises

Deferred asynchronous operations

photo by Alper Çuğun

Promise as token

Broken promises

  • "That's not on the menu!"
  • "We closed the shop already!"
  • "The kitchen is closed!"
  • "I hate this person. I refuse."

Promises - states

  • Pending.
  • Fulfilled.
  • Rejected.
  • then - handle fulfill/reject + chain promises.
  • catch - errors in the chain.
var p1 = Promise.resolve("fulfilled!");
p1.then((msg) => console.log(msg)); // "fulfilled!"

var p2 = Promise.reject("rejected!");
p2.then(undefined, (msg) => console.log(msg)); // "Rejected!"

var p3 = new Promise((resolve, reject) => {
  if (condition) resolve();
  if (otherCondition) reject();
  throw new Error("Error!");
});

//fulfill, reject, catch
p3.then(success, failure)
  .catch(recover);
var p = new Promise((resolve,reject)=>{
  //I'm on vacation in July!
  if(new Date().getMonth() === 7){
    reject(new Error("On vacation!"));
  } else {
    resolve("Yeah, do it!");
  }
});
p.then(
  successMsg => console.log(successMsg),
  rejectMsg => console.log(rejectMsg)
);
function canIdoTheWork(date){
  return (date.getMonth() === 7)?
      Promise.reject(new Error("On vacation!"))
    : Promise.resolve("Let's do this!");
}
canIdoTheWork(new Date()).then(
  successMsg => console.log(successMsg),
  rejectMsg => console.log(rejectMsg)
);
var barista = {
  skills: new Set(['Americano', 'Latte']),
  get mood() { return Math.round(Math.random()) },
  makeCoffee(type = 'Americano') {
    if (!this.skills.has(type)) {
      return Promise.reject(new Error(`No ${type} for you!`));
    }
    return new Promise((resolve, reject) => {
      // 1 second to make a coffee
      setTimeout(() => { // 1 second to make a coffee
        if(this.mood){
          return resolve(`Here is your ${type}`);
        }
        reject(new Error("I quit!"));
      }, 1000);
    });
  }
}
var success = msg => console.log(msg, "thanks!");
var fail = err => console.log(err);

//reject straight away!
barista.makeCoffee("milkshake").then(undefined, fail);

//Make me an Americano and a latte
barista.makeCoffee()
  .then(() => barista.makeCoffee("Latte"))
  .then(success).catch(fail);

Awaiting multiple promises

var car1 = new Car("one"),
var car2 = new Car("two"),
var car3 = new Car("three"),
var carRace = [car1.drive(), car2.drive(), car3.drive()];

Promise.race(carRace).then(e => console.log(e));
Promise.all(carRace).then(data => console.log(data));

Part 2 - Iterables

How does JS determine if something is iterable?

Symbol.iterator

A zero arguments function that, when called:

  • Returns an object.
  • has .next() method.
  • returns Object:
    • with a .done and .value property
// A zero arguments function that, when called:
obj[Symbol.iterator] = function() {
  // Returns an object
  return {
    // has .next() method.
    next() {
      // that returns an object with:
      return {
        value: "", // value prop
        done: trueOrFalse, // done prop
      };
    }
  };
};
      

Iterables

Class Example Result String s = "hello" "h", "e" ... Set var set = new Set([1,2,3]) 1,2,3 Map new Map([["name", value]]) ["name", value] NodeList document.querySelectorAll("*") HTMLElement

Fancier Iterators (example)

  • map.keys()
  • map.values()
  • map.entries()

Part 3 - Generator functions

Problem

Hard to represent lazy lists in JS.

Generator functions

function* myGenerator(){
  yield 1;
}
var genObj = myGenerator();
        

yield

Yield control of execution at this point.

Generator functions

function* myGenerator(){
  yield 1;
}
var genObj = myGenerator();
var result = myGenerator.next(); //run!
// result = Object { value: 1, done: false }
        

{value: 1, done: false}

This looks familiar...

Generator functions

function* myGenerator(value){
  yield 1;
  return value;
}
var genObj = myGenerator("hello there");
var result = genObj.next(); //run!
// result = Object { value: 1, done: false }
result = genObj.next(); //keep going!
// result = Object { value: "hello there", done: true }
        

Generator Object API

  • .next(any value) => {value, done}
  • .throw(Error) => {value, done}
  • .return(value) => {value, done}

Generator functions

function* myGenerator(value){
  let result = yield value;
  return result;
}
var genObj = myGenerator("hello there");
var value = genObj.next().value; //first run!
genObj.next(value.toUpperCase());
console.log(genObj.next().value); // "HELLO THERE"
        

What if the yielded value is bad?

function* myGenerator(value){
  let result = "";
  try{
    result = yield 1;
  } catch (err) {
    result = "recovered";
  }
  return result;
}
var genObj = myGenerator("hello there");
var result = genObj.next().value;
if(typeof result !== "string"){
  result = genObj.throw(new TypeError("Expected a string"));
}
console.log(result.value); // "Recovered"
        

Part 4 - Let's implement async/await!*

*terms and conditions apply.

Promises everywhere...

fetch(url)
  .then(response => response.json())
  .then(json => processJson)
  .catch(handleError)
  .then(moarThen)
  .catch(err => throw err)
        

Tomorrow...

async function getJSON(url){
  let response = await fetch(url);
  let result;
  try{
    let json = await response.json();
    result = processJson(json);
  } catch (err) {
    result = attempToRecover(err);
  }
  return result;
}
getJSON(someURL)
  .then(showIt)
  .catch(handleError);

How does it work?

  • Exploits generators + promises.
  • Flow control and error handling.

Generator

function* getURL(url){
  //...
}
// Generator object - initializes closure
let getFoo = getURL("/foo");
        

Generator Object API (revisited)

  • .next(any value) => {value, done}
  • .throw(Error) => {value, done}
  • .return(value) => {value, done}

Generator

function* getURL(url){
  let response = yield fetch(url);
}
// Generator object - initializes closure
let getFoo = getURL("/foo");
let {value: promise} = getFoo.next();
        

Generator

function* getURL(url){
  let response = yield fetch(url);
  return yield response.text();
}
// "Generator" object;
let getFoo = getURL("/foo");
// Prime the pump - run to first yield!
let {value: promise} = getFoo.next();
promise
  .then(res => getFoo.next(res).value)
  .then(text => console.log(text));

Error handling

promise
  .then(res => getFoo.next(res).value)
  .then(text => console.log(text))
  .catch(err => getFoo.throw(error));

Need to handle multiple "steps"

async(function* getJSON(url){
  let response = yield fetch(url);
  let result;
  try{
    let json = yield response.json();
    result = processJson(json);
  } catch (err) {
    result = attempToRecover(err);
  }
  return result;
});

Let's build it!

Bad times...

  • Don't forget yield/await
  • window.onload = async function foo(){...}
  • calling foo() with no `.catch`
Promise, generators, async/await