Callbacks Thunks Promises Generators Event reactive (observables) CSP
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
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();
});
There are trust issues when giving a callback to a third party service
E.g. trust that they call the callback:
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
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');
});
});
});
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:** **
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
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
Observables