asynchrony

ECMAScript has never had asynchrony...

Until ES2015

Bro, I started setting timeouts and handling click events when you were still wetting the bed.

Probs - but the hosting environment is not the ECMAScript language specification.

The Event Loop

Now and Later

callbacks

A callback is a function invoked or "called back into" by the event loop 

var express = require('express');  
var fs = require('fs');  
var app = express();

app.post('/process-file', function(req, res) {  
  var inputFile = 'input.txt';
  var outputFile = 'output.txt';

  fs.readFile(inputFile, function(err, data) {
    if (err) return res.status(500).send(err);

    process1(data, function(err, data) {
      if (err) return res.status(500).send(err);

      process2(data, function(err, data) {
        if (err) return res.status(500).send(err);

        process3(data, function(err, data) {
          if (err) return res.status(500).send(err);

          fs.writeFile(outputFile, data, function(err) {
            if (err) return res.status(500).send(err);

            res.status(200).send('processed successfully using callback hell');
          });
        });
      });
    });
  });
});

Programming with callbacks be like:

  • Callback Hell
  • Called too early
  • Called too late
  • Called too few or too many times
  • Failure to pass along necessary params
  • Swallowed exceptions

Inverting the Inversion of Control

Job Queue

A Job is an abstract operation that initiates an ECMAScript computation when no other ECMAScript computation is currently in progress... Execution of a Job can be initiated only when there is no running execution context and the execution context stack is empty. A PendingJob is a request for the future execution of a Job... Once execution of a Job is initiated, the Job always executes to completion. No other Job may be initiated until the currently running Job completes. However, the currently running Job or external events may cause the enqueuing of additional PendingJobs that may be initiated sometime after completion of the currently running Job. A Job Queue is a FIFO queue of PendingJob records... A request for the future execution of a Job is made by enqueueing, on a Job Queue, a PendingJob record that includes a Job abstract operation name and any necessary argument values. When there is no running execution context and the execution context stack is empty, the ECMAScript implementation removes the first PendingJob from a Job Queue and uses the information contained in it to create an execution context and starts execution of the associated Job abstract operation. The PendingJob records from a single Job Queue are always initiated in FIFO order. 

Promises

var fs = Promise.promisifyAll(require('fs'));  
var app = express();

app.post('/process-file', function(req, res) {  
  var inputFile = 'input.txt';
  var outputFile = 'output.txt';

  fs.readFileAsync(inputFile)
    .then(Promise.promisify(process1))
    .then(Promise.promisify(process2))
    .then(Promise.promisify(process3))
    .then(fs.writeFileAsync.bind(fs, outputFile))
    .then(function(data) {
      res.status(200).send('processed successfully using bluebird promises');
    })
    .catch(function(err) {
      res.status(500).send(err);
    });
});

Programming with Promises be like:

  • Composable
  • Never called too early
  • Never called too late
  • Promises are only resolved once
  • Promises have at most one resolution value 
  • Errors are never swallowed*

Promises are awesome, but it leaves much to be desired in expressing async flow control.

Iterables and Iterators

A new construct to consume data

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

console.log(iter.next());

{ 
  value: 'a', 
  done: false 
}

console.log(iter.next());

{ 
  value: 'b', 
  done: false 
}

console.log(iter.next());
  
{ 
  value: 'c', 
  done: false 
}

console.log(iter.next());

{ 
  value: undefined, 
  done: true 
}

for... of

let arr = ['a', 'b', 'c'];

for (let x of arr) {
  console.log(x);
}

a
b
c

Generators

var Promise = require('bluebird');  
var fs = Promise.promisifyAll(require('fs'));  
var express = require('express');  
var run = require('../../lib/runner');

var app = express();

app.get('/', function(req, res) {  
  run(function *() {
    var inputFile = 'input.txt';
    var outputFile = 'output.txt';

    try {
      var inputData = fs.readFileAsync(inputFile);
      var processedData1 = yield Promise.promisify(process1)(inputData);
      var processedData2 = yield Promise.promisify(process2)(processedData1);
      var result = yield Promise.promisify(process3)(processedData2);

      yield fs.writeFileAsync(outputFile, result);
      res.status(200).send('success');

    } catch (err) {
      res.status(500).send(err);
    }
  });
});

Programming with Generators be like:

  • Suspension of function execution
  • Cooperative Concurrency
  • Input / Output messaging 

The Future

Async Await

async function foo() {
    try {
    var values = await getValues();
 
        var newValues = values.map(async function(value) {
            var newValue = await asyncOperation(value);
            console.log(newValue);
            return newValue;
        });
        
        return await* newValues;
    } catch (err) {
        console.log('We had an ', err);
    }
}

Observables

function getMouseDrags(elmt) {
  var mouseDowns = Observable.fromEvent(elmt, "mousedown"),
  var documentMouseMoves = Observable.fromEvent(document.body, "mousemove"),
  var documentMouseUps = Observable.fromEvent(document.body, "mouseup");

  return mouseDowns.concatMap(mouseDown =>
    documentMouseMoves.takeUntil(documentMouseUps));
};

var image = document.createElement("img");
document.body.appendChild(image);

getMouseDrags(image).forEach(dragEvent => {
  image.style.left = dragEvent.clientX;
  image.style.top = dragEvent.clientY;
});

CSP

Communicating Sequential Processes

 

C. A. R. Hoare

function* player(name, table) {
  while (true) {
    var ball = yield csp.take(table);
    if (ball === csp.CLOSED) {
      console.log(name + ": table's gone");
      return;
    }
    ball.hits += 1;
    console.log(name + " " + ball.hits);
    yield csp.timeout(100);
    yield csp.put(table, ball);
  }
}

csp.go(function* () {
  var table = csp.chan();

  csp.go(player, ["ping", table]);
  csp.go(player, ["pong", table]);

  yield csp.put(table, {hits: 0});
  yield csp.timeout(1000);
  table.close();
});

Questions?

deck

By Forest Toney