Lecture 17 Asynchronous Programming

Motivation

History

Sequential Programming

  • We write code that does one thing at a time
  • If we get stuck (e.g. awaiting user input) everything stops

Asynchronous Programming

  • In modern systems, lots going on at once
    • Computation
    • Animation
    • User input
    • Network communication
  • Often need to start something now and finish it later
  • How do we express that in code?
    • Start this, and while waiting start that, and while waiting...

Two-edged Sword

Benefits

  • Exploit available parallelism
  • Do 10 things are once, finish 10 times faster
  • Continue to be responsive while waiting for something slow

Costs

  • Want result immediately, have to wait
    • stay responsive to user while waiting
    • have to remember what I was waiting for
  • Asynchrony is confusing
    • code is complex, hard to read
    • source of many bugs
    • hard to debug
    • e.g. tracing execution is naturally sequential

Example: Script Loading

  • Standard (non-module) script loading is synchronous
    • page load pauses while script is fetched, executed
    • one thing at a time
  • Script at beginning loads/runs before page complete
    • delays parsing of rest of page
    • cannot count on page being present
    • must take care manipulating page
  • Script at end loads/runs after page complete
    • Delays download of script
    • Delays execution of script
    • May reduce responsiveness
<script src="early.js">
<div>
content here...
</div>
<script src="late.js">

Example: Script Loading

<script src="s1.js"></script>
<!--page fetch pauses here while s1.js is fetched and executed-->
<script defer src="s3.js"></script>
<!--page fetch continues while s3.js is fetched-->
<!--s3.js executes after page is loaded-->
<!--fetch in parallel, execute in sequence-->
<script type="module" src="s3.js"></script>
<!--implicitly deferred-->
<script async src="s2.js"></script>
<!--page fetch/exec continues while s2.js is fetched and executed-->
<!--fetch/execute both in parallel—more parallel than defer-->

More General Solutions

Polling

Example

We want to

Polling

let img=document.createElement('img');

//setting src triggers load
img.src='https://pictures.com/mine.jpg'
while (!img.complete) {
// twiddle my thumbs
}

init();

Drawbacks

  • While waiting, you're doing nothing
  • Except wasting energy on empty processing
  • Maybe you could be doing something else meanwhile...

Polling

Sometimes Necessary

  • “uncooperative” monitoring
  • e.g., keep track of whether a page on another web site has changed
  • it won't tell you, so you have to keep asking

Drawbacks

  • While waiting, you're doing nothing
  • Except wasting energy on empty processing
  • Maybe you could be doing something else meanwhile...

Callbacks

Callbacks

Example: SetTimeout

  • Specifies something that should happen later
  • First argument is callback to run later
  • Second argument is delay in ms
function missMe() {
  button.textContent="did you miss me?"
 }
button.addEventListener("click",
 evt => {
  button.textContent = "hello";
  setTimeout(missMe, 3000);
  }
 );

What's Wrong?

  • Specifies something that should happen later
  • First argument is callback to run later
  • Second argument is delay in ms
  • Callback invoked before invoking setTimeout
  • shouldn't be evaluating it here
function missMe() {
  button.textContent="did you miss me?"
 }
button.addEventListener("click",
 evt => {
  button.textContent = "hello";
  setTimeout(missMe(), 3000);
  }
 );

Example: SetInterval

  • Specifies something that should happen periodically
  • First argument is callback to run later
  • Second argument is delay in ms
let i=0;
let tick =
  () => {button.textContent=++i};
button.addEventListener("click",
  evt => {
	  setInterval(tick, 500);
  }
);

Events

Events

Example

We want to

Load Event Listener

let img=document.createElement('img');

img.addEventListener('load',init);
img.src='https://pictures.com/mine.jpg'

doOtherStuff(); //runs during img load
  • load event is triggered when item finishes loading
  • event listener is a callback
  • system invokes callback when event occurs
  • Drawback: code does not execute in order you see
  • many opportunities to get confused

What's Wrong?

let img=document.createElement('img');

img.src='https://pictures.com/mine.jpg'
img.addEventListener('load',init);

doOtherStuff(); //runs during img load

Race Conditions

  • Triggered load before adding listener
    • may finish before listening starts
    • won't ever hear event
  • Common problem in async programming
    • Things happen in an unexpected order
  • Very hard to debug
    • When rerun, different order happens

More Events?

let img=document.createElement('img');
let img2=document.createElement('img');
let img3=document.createElement('img');

img.addEventListener('load',init);
img2.addEventListener('load',init);
img3.addEventListener('load',init);

img1.src=img2.src
    =img3.src='https://pictures.com/mine.jpg'

doOtherStuff();

What's wrong?

  • init after each event
  • How can I init after all events?

More Events?

img.addEventListener('load',event => {
  img2.addEventListener('load', event => {
    img3.addEventListener('load',init);
    img3.src="http://pictures.com/mine.jpg";
    }
  img2.src="http://pictures.com/mine.jpg";
  }
img1.src='http://pictures.com/mine.jpg';

doOtherStuff();

Drawbacks?

  • Code is not executed in order of code lines
  • Have to think hard about where to put addEventListener to catch events
    • arbitrarily deep block nesting
    • visit Callback Hell
    • bugs are common in complex code structures
  • 2d load starts after first completes, and 3d after 2d
    • only one load at a time
    • takes 3 times as long as doing all at the same time

Async/Await (v1)

Async/Await

  • Usage:
    • async specifies that a function is asynchronous
    • await tells execution to pause, await result of async function
    • using await makes your code asynchronous
    • so only permitted in a function declared async
  • Common use case: fetching web content
    • fetch gets response from a URL
    • json() parses json out of a response
async function work() {
  let response = await fetch("countries.json");
  let data = await response.json();
  for (let country of data.country) {
    var div = document.createElement("div");
    div.innerHTML =
      `${country.code} ${country.name}`;
    document.body.appendChild(div);
  }
}
work();

Promises

Promises

Goal

  • Define code syntax that looks how we think
  • fetch img1 then fetch img2 then fetch img3 then init
  • Represent order of actions and intents to wait
  • Problem: can't pass code as an argument
  • Solution: wrap in a callback function
  • Pass in result of previous step

Idea

loadImage(url1)
.then({img1=result})
.then(loadImage(url2)
.then({img2=result})
.then(loadImage(url3)
.then({img3=result})
.then(init);

Promise

Goal

  • Define code syntax that looks how we think
  • fetch img1 then fetch img2 then fetch img3 then init
  • Represent order of actions and intents to wait
  • Problem: can't pass code as an argument
  • Solution: wrap in a callback function
  • Pass in result of previous step

Example

loadImage(url1) //returns a promise
  .then(result => img1=result)
  .then(() => loadImage(url2))
  .then(result => img2=result)
  .then(() => loadImage(url3)
  .then(result => img3=result)
  .then(init);	//no arrow;
  //init is a function

Parallel Promises

Goal

  • Define code syntax that looks how we think
  • simultaneously fetch img1, img2, img3 then init
  • Represent order of actions with implicit waits

Example

Promise.all([
  loadImage(url1)
  loadImage(url2)
  loadImage(url3)])
.then([u1,u2,u3] => {
  //note destructuring bind
  img1=u1;
  img2=u2;
  img3=u3;
})
.then(init);

Using Promises

Promise

Passing Results

  • Promise is “thenable”
  • then(f) says f is a callback to invoke when promise is fulfilled
    • f receives value of fulfilled promise
    • add any number of callbacks any time
  • No race condition:
    • Callback provided before fulfillment is invoked upon fulfillment
    • Callback provided after fulfillment is invoked immediately
p = delayed("hi",1000);
// promise resolves to "hi"
// after 1000ms

p.then(res => console.log(res));
// console outputs 1000ms later
...lots of code
p.then(res => console.log("bye"));
// if p already fulfilled,
// callback executes immediately

Chaining

wait(1000)	//promise fulfilled after 1000ms
.then(() => console.log(1))
.then(() => console.log(2))
.then(() => console.log(3))
What gets output?
  1. delay, 1,2,3
  2. delay, 3,2,1
  3. 2, delay, 1, 3
  4. 3, 2, 1, delay

Chaining

wait(1000)	//promise fulfilled after 1000ms
.then(() => console.log(1))
.then(console.log(2))
.then(() => console.log(3))
What gets output?
  1. delay, 1,2,3
  2. delay, 3,2,1
  3. 2, delay, 1, 3
  4. 3, 2, 1, delay

Chaining Results

  • then() callback can return a value (or a promise of one)
  • passed to the next then() callback in chain when available
  • more precisely, then() returns a new promise p providing that value
  • so next then() is invoked on p
  • p.then() passes on the value of p
//traditional evaluation style
y = f(g(h(x)));

//promise-based
Promise.resolve(x)
// promise with fulfilled value x
.then(h) //h receives x
.then(g) //g receives h(x)
.then(f) //f receives g(h(x))
.then(res => y=res);

Chaining Results

Puzzle

  • sayOne() and sayTwo() return promises
    • To eventually say one or two
  • How do these behaviors differ?
    • What gets executed when?
    • What values get passed where?
sayOne().then(sayTwo);

sayOne().then(sayTwo());

sayOne().then(function () {
  sayTwo;
});

sayOne().then(function () {
  sayTwo();
});
Notes
  • fetch() allows us to send an HTTP request and fetch a remote resource.
  • To be able to fetch a resource via JavaScript it must be CORS-enabled.
  • fetch() returns a Promise that resolves with the response.
  • Activity: Change this to display a flag instead of the country code! There are flag images in img/flags in PNG format, with the country code as their filename.

Parallel Promises

  • Promise.all() tracks multiple promises running in parallel
  • Returns one promise
    • resolved when all its subpromises are
    • value is array of individual promises' values
    • rejects if any of its individuals do
  • Much clearer about intent than nested event callbacks
    • so much easier to debug
let p1=fetch(url1);
let p2=fetch(url2);
let p3=fetch(url3);
Promise.all([p1,p2,p3])
.then([c1,c2,c3] =>
{//process all 3 pieces},
err => {handle the error})

Error Handling

  • Sometimes, an asynchronous computation wants to report failure or errors
    • Could just pass the error to then() callback
    • But the pattern is so common we design for it
  • Promise can be rejected instead of fulfilled
  • then() can take a second callback
    • invoked when promise rejected
    • receives value of rejected promise---usually error message
    • like first callback, can return a value or promise
  • whichever callback is invoked, returns promise to invokes next then()
fetch(url)
//returns a promise to provide
//   content found at that url
.then(content => {
  //executes if fetch is fulfilled
  console.log("received" + content);
  return 0},
error => {
  //executes if fetch is rejected
  console.log("error: " + error)
  return -1}
})
.then(res => console.log(res))
//logs 0 if fetch succeeded,
//     1 if it failed

Summary

Some Other Methods

Constructing Promises (Wrong Way)

  • promise needs to know when it is fulfilled or rejected
    • and what fulfilled/rejected value is
  • How can promise receive that info?
    • With function or method calls
  • So just expose some methods?
    • those methods are expose to anyone
    • unexpected invocations can make bugs
    • how might we hide these methods from misuse?

Bad Idea

var p = new Promise();
p.fulfill(17);
p.reject(17);

Constructing Promises

  • Hide fulfill and resolve functions inside closures
  • promise constructor gives you two callbacks
    • fulfiller(value) to call when promise should fulfill
    • rejector(value) to call when promise should rejects
    • Hide both inside a closure
  • How does promise communicate these callbacks to you?
    • you give the constructor a callback
    • your callback begins the promised work
    • and prepares to invoke fulfiller or rejector when done
function prepareToResolve(fulfiller, rejector) {
    function myCallback(result) {
        //invoked when async work completes
        if (isGood(result)) {
            fulfiller(result)
            } else {
            rejector("error computing result");
        }
    }
    asynchronousFunction(myCallback);
}
myPromise = new Promise(prepareToResolve);

Example

  • Common use of Promise constructor is to wrap callback-based APIs
  • Make them simpler to use
announce = () => console.log("10 seconds passed");
setTimeout(announce, 10*1000); //old way

function wait (ms) { //promise constructor
    return new Promise(fulfiller =>
        setTimeout(fulfiller, ms));
    //no failures to handle, so ignore rejector
}

wait(10*1000)
.then(announce).catch(failureCallback);

Async and Await

Async/Await

  • async declares an asynchronous function
    • Any async function returns a Promise that will be resolved when the function finishes and returns its “actual” value
    • without hassle of Promise constructor
  • async function can await any promise
    • await appears synchronous, but it makes that async function pause and wait for the promise to resolve
    • when the promise resolves, execution continues from the pause point
async function doMyWork() {
  console.log('beginning')
  let response =
    await fetch("countries.json");
  let data = await response.json()
  console.log(data);
  for (let i=1; i<=5; i++) {
    let result =
	  await delayed('answer',5000);
    console.log(i + result);
    }
  return data;
  }

Async/Await is Syntactic Sugar for Promises

Async

async function f () {
  x();
  a = await(y());
  return z();
  }

Promise

function f () {
  x();
  p = y()
  .then((res) => {a = res;})
  .then(z)
  return p
  }

Async Always Returns a Promise

Compare/Contrast