Lecture 15 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

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();
			  });

		  
  • 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

  • promise needs to know when it is fulfilled or rejected
    • and what fulfilled value is
  • How does (your) code communicate with (other, promise) code?
    • With function or method calls
  • so promise gives you two callbacks
    • fulfiller(value) to call when promise should fulfill
    • rejector(value) to call when promise should rejects
  • How does promise communicate these functions 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