johnny-dreamguns
10/7/2019 - 9:21 PM

Async Programming

Async Programming

Patterns

Callbacks Thunks Promises Generators Event reactive (observables) CSP

Parallel Vs Async

Parallel Executing multiple things on different threads / cores of a CPU

Parallelism improves performance, it’s about optimisation

Async Our programs in JS are run on a single thread

Concurrency Two higher level operations happening within the same time frame** **

E.g. making an Ajax call and scrolling the page or repainting the screen, the events are executing one at a time so that nothing freezes

When an Ajax call completes, the event to call it’s callback is added to the bottom of the event queue

Sync calls freeze the page

Async programming is about managing concurrency

Callback Hell

Inversion of control - One of the two evils of callback hell There’s a part of the program that I am in charge of executing and there’s another part I’m not in control of executing

There’s a part that executes now and a second part that executes in a callback, when the callback is given to someone else to execute, that is the inversion of control. We are not in control of when the callback executes, hence inversion of control

Example 1

setTimeout(function() {
  console.log(’test’);
}, 1000);

We have control of lines 1 and 3, line 2 is handled by the setTimeout function

Example 2

trackCheckout ( purchaseInfo, function finish() { 
  chargeCreditCard(purchaseInfo); 
  showThankYouPage(); 
});

Issues with callbacks - 1 Trust

There are trust issues when giving a callback to a third party service

E.g. trust that they call the callback:

  • not too early
  • not too late
  • Not too many times
  • Not too few times
  • No lost context
  • No swallowed errors

Issues with callbacks - 2 Not reasonable

Our brains our single threaded in a similar way to JavaScript, our programs are easier to understand if they follow a similar style

Callbacks make things more complex than they need to be

When one thing depends on another to finish before it can this is called temporal dependency

With callbacks the only way to express temporal dependencies is through nesting

The problem with nesting is we no longer get to progress through the code linearly / sequentially / synchronous looking way

It is possible to do async code in a sync looking fashion

Thunks

A thunk is a function that has everything it needs to get you a value back, you don’t need to pass any arguments in, you simply call it

It is a function with some closured state keeping track of a value

Thunks allow us to pass the wrapper around some state instead of passing the value or the state itself. It becomes a token that represents that value

A thunk is a much less sophisticated version of a promise, both are wrappers around values

Sync thunk example A sync thunk takes no parameters

function add(x,y) {
  return x + y;
}

var thunk = function() {
  return add(10, 15);
}

thunk(); // 25

Async thunk example An async thunk takes one parameter, a callback**

function addAsync(x,y,cb) {
  setTimeout(function() {
    cb(x + y);
  }, 1000);
}

var thunkAsync = function(cb) {
  return addAsync(10, 15, cb);
}

thunkAsync(function(response){
  console.log(response); // 25
}); 

Active Thunk VS Lazy thunk An active thunk does some work and holds on to the response, a lazy thunk doesn’t do the work until you call it

**Importance of Thunks ** By using the closure within the thunk pattern to maintain the state of something, we eliminate time as a tricky factor

Time is the trickiest thing we have to deal with in an application

function getFile(file){
  let text, fn;
  
  fakeAjax(file, function(response){
    if(fn) fn(response);
    else text = response;
  });
  
  return function(cb){
    if(text) cb(text);
    else fn = cb;
  }
}

let th1 = getFile('file1');
let th2 = getFile('file2');
let th3 = getFile('file3');

th1(function(text){
  output(text);
  th2(function(text){
    output(text);
    th3(function(text){
      output(text);
      output('Complete');
    });
  });
});

Promises

A promise is a container that wraps around a value

Promises are a codification of the idea that we need a placeholder to eliminate time as a concern wrapped around the value

This is known in programming as a future value or a monad

It’s a value that at some point in the future will become fulfilled but in the meantime we still need to be able to reason about it

Promises don’t have the inversion of control issues of using callbacks

Promises can be thought of as a callback manager, it manages our callbacks in a trustable fashion

Promise API Using event listeners of ‘complete’ and ‘error’ uninvert the issue with inversion of control, it brings sanity back to the situation

We subscribe and unsubscribe to events

The trust issues with callback hell are solved by this

var promise = trackCheckout(purchaseInfo)

promise.then(
  finish,
  error
);

Promises are a bit like callbacks But they solve all sorts of issues that we have with callback functions like:** **

  • Only resolved once
  • Either success or fail
  • Messages passed
  • Exceptions become errors
  • Value is immutable once resolved

Because the value is immutable it can be passed around to other symptoms without concern that it could be changed

Flow Control Flow control is managing how many async calls are called and ordered

Chaining Promises

doFirstThing()
.then(function() { return doSecondThing(); })
.then(function() { return doThirdThing(); })
.then( complete, Error);

Chaining promises displays as a vertical chain rather than a nested pyramid

Another example of chaining promises

function getFile(file){
  return new Promise((resolve, reject) => {
    fakeAjax(file, resolve);
  });
}

function output(text){
  console.log(text);
}

let p1 = getFile('file1');
let p2 = getFile('file2');
let p3 = getFile('file3');

p1
.then(output)
.then(() => p2)
.then(output)
.then(() => p3)
.then(output)
.then(() => output('Complete'));

Inline arrow functions should be kept to a minimum and one line or two maximum to make it readable

The example above with everything as a separate step is ideal in promise chaining

Chaining an arbitrary number of promises Here we use map to convert an array of urls to an array of promises

Reduce turns it into a chain of promises

function getFile(file){
  return new Promise((resolve, reject) => {
    fakeAjax(file, resolve);
  });
}

['file1', 'file2', 'file3', 'file1']
.map(getFile)
.reduce((chain, pr) => {
  return chain.then(() => { return pr; }).then(output);
}, Promise.resolve())
.then(() => console.log('Complete'));

Promise.all This allows us to do multiple requests, fire them in parallel and get the result

This is a ‘gate’, all need to be completed before we can continue but can happen in any order

Promise.race This fires multiple requests, whichever finishes first is processed

Generators

A generator is a pausable function, you can pause and resume as many times as necessary

Generators are about solving the non local, non sequential reasonability problem

In JavaScript all functions have a run to completion semantic

At any one moment only one function is running

Generators do not have a run to completion semantic

A generator is a syntactic form of declaring a state machine

A state machine is a patterned series of flow from one state to another state and listing all the transitions out

It’s possible to make state machines without generators but it takes a lot of work

When the generator is waiting it doesn’t block the whole program

Example of a generator

function *gen(){
  console.log('Hello');
  yield;
  console.log('World');
}

let it = gen();
it.next();
it.next();

Calling a generator function creates an iterator

Yield can pass values

function *main() {
yield 1;
yield 2;
yield 3;
}

let it2 = main();
it2.next(); // { value: 1, done: false }
it2.next(); // { value: 2, done: false }
it2.next(); // { value: 3, done: false }
it2.next(); // { value: undefined, done: true }

For of loop Because a generator is an iterative algorithm you can loop over the values they return using the new ES6 for of loop

for (let o of main()) {
console.log(o);
}

It’s possible to pass values to a generator too

function* run (){
  let x = 1 + (yield);
  let y = 1 + (yield);
  yield (x + y);
};

let it = run();
it.next();
it.next(10);
console.log(it.next(30).value);

Using a generator with while true

function* runInfinite (){
  while(true){
    yield 'test';
  }
};

let it2 = runInfinite();
console.log(it2.next()); // { value: 'test', done: false }
console.log(it2.next()); // { value: 'test', done: false }
console.log(it2.next()); // { value: 'test', done: false }
console.log(it2.next()); // { value: 'test', done: false }

Synchronous looking async code // Async generator

function getData(d) {
  setTimeout(function(){ it.next(d); }, 1000);
}

function* run (){
  let x = 1 + (yield getData(10));
  let y = 1 + (yield getData(30));
  let answer = yield (getData(x + y));
  console.log(answer);
};

let it = run();
it.next();

Thenables These aren’t quite the same as promises but can be used in a similar way

To look up later

Observables