JavaScript

Asynchrony & Concurrency

UMN Code-People, May 5 2016

David Naughton - UMN Libraries Web Development

naughton@umn.edu

Slides

http://z.umn.edu/jscp201605

Synchrony vs. Asynchrony vs. Concurrency vs. Parallelism

Source Code


function1();
function2();
function3();
          

Synchrony

Concurrency

"...several computations are executing during overlapping time periods—concurrently—instead of sequentially..." — Wikipedia: Concurrent computing

Many examples...

Concurrency: Parallelism

Concurrency: Asynchrony

So what?

"I don't care when these execute!"


Reveal.addEventListener("event1" function1, false);
Reveal.addEventListener("event2" function2, false);
Reveal.addEventListener("event3" function3, false);
          

"Maybe you just don't understand functional programming."

Async functions in sequence?

X


const result1 = function1();
const result2 = function2(result1);
const result3 = function3(result2);
          

Async & try-catch?

X


try {
  const result1 = function1();
  const result2 = function2(result1);
  const result3 = function3(result2);
} catch(e) { }
          

What's going on?

Isn't the JavaScrip/node.js event loop single-threaded?

What the heck is the event loop anyway?

Hybrid Environment

  • Sync & async ops (slow & unpredictable stuff, e.g. IO, click events).
  • Sync: Straight to stack, executed immediately.
  • Async: May be executed off-stack, in multi-threaded hosting environment (browser or OS). callback(result) to event queue.
  • When stack is empty, event loop moves events/callbacks from queue to stack.
  • This is why node.js is fast.

So how to run async functions sequentially?

We'll focus mostly on this one problem. An evolution of approaches...

Nested Callbacks

>


function1(function (err, result1) {
  (err ? console.log(err) : 
    function2(result1, function(err, result2) {
      (err ? console.log(err) :
        function3(result2, function(err, result3) {
          (err ? console.log(err) :
            // Do something with result3
          ); 
        });          
      );
    });
  );
});
          

The infamous "callback hell", or "pyramid of doom", or arrow anti-pattern.

Callbacks: Pros & Cons

Pros

  • Speed.
  • Long history of use & support.

Cons

Promises

A proxy for the pending result of an async function, to be fulfilled or rejected later.

Callbacks vs. Promises

While we give up control with callbacks...


asyncFunction(function pleaseCallThis(err, result) {
  // ...but only once, and please let me know about any errors!
});
          

...we keep it with promises:


const promise = asyncFunction();
promise.then(
  function fulfilled (result) {},
  function rejected (err) {}
);
          

Promises: Async Sequences


function1()
.then(function (result1) {
  function2(result1);
})
.then(function (result2) {
  function3(result2);
})
.then(function (result3) {})
.catch(err);
          

Promises: Parallelism


function task() {
  const p1 = subtask1();
  const p2 = subtask2();
  const p3 = subtask3();
  Promise.all([p1, p2, p3]).then(function(results){});
}
          

Promises: Pros & Cons

Pros

  • Keep control: Guaranteed to be resolved only once, but can be observed many times.
  • Much easier to reason about.
  • Supported everywhere with packages like Bluebird, with Bluebird.promisify() for callback-based functions.

Cons

  • May be a little slower.
  • Still not well-understood by many developers.
  • Some subtle gotchas with error-handling.
  • Promises still use callbacks, just in a friendlier way.

There's an even better way...

...but it requires some setup.

Generators

Generators are iterator factories, defining iterative algorithms with single functions that maintain their own state, which can be paused and resumed.

Generators: Simple Example


function *naturals() {
  var integer = 1;
  while(true) {
    yield integer++;
  }
}

const it = naturals();
console.log(it.next().value); // 1
console.log(it.next().value); // 2
console.log(it.next().value); // 3
          

Generators: Input


function *lc() {
  while(true) {
    var word = yield null; // yield consumes input
    console.log(word.toLowerCase());
  }
}

const it = lc();
it.next(); // Value would be ignored, only executes up to yield.
it.next('Hello'); // hello
it.next('World'); // world
it.next(42); // TypeError!
          

Generators: External Exception-Handling


function *lc() {
  while(true) {
    var word = yield null; // yield consumes input
    console.log(word.toLowerCase());
  }
}

const it = lc();
it.next();
try {
  it.next(42); 
} catch(e) { } // TypeError
          

Generators: Internal Exception-Handling


function *lc() {
  while(true) {
    try {
      var word = yield null; // yield consumes input
      console.log(word.toLowerCase());
    } catch (e) {} // Exceptions caught here.
  }
}

const it = lc();
it.next();
try {
  it.throw('Sending the generator an exception.'); 
} catch(e) { } // Never gets here.
          

Generators + Promises

What if we yield a promise from a generator?


function *gen() {
  try { var result = yield asyncFunction(); }
  catch (e) { console.error(e); }
}

var it = gen();
var p = it.next().value;
p.then(
    function fulfilled(result) { },
    function(err){ gen.throw(err); }
);
          

Coroutines


Bluebird.coroutine(function *() {
  try { 
    // functions return promises:
    const result1 = yield function1();
    const result2 = yield function2(result1);
    const result3 = yield function3(result2);
  }
  catch (e) { console.error(e); }
  return result3;
});
          

But are these really coroutines?

"...computer program components that generalize subroutines for nonpreemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations." — — Wikipedia: Coroutine

Generators are already semi-coroutines.

""Subroutines are special cases of ... coroutines." — Donald Knuth, "The Art of Computer Programming"

Coroutines: Pros & Cons

Pros

  • Make asynchronous code look almost identical to synchronous code. Very easy to reason about.
  • Keep all the control we get from promises.
  • Coroutines probably core ES soon: aysnc/await proposal for ES7

Cons

  • May be a little slower.
  • Not well-understood by most developers.
  • Generators not available everywhere without transpilers like Babel.

Explore Further