The Story of 3 files

JS Async Patterns

http://bit.ly/js-async

Salama Ashoush

Software Developer at Orange Labs

Part-time Front-end developer at Pushbots

ITI OSAD

I love Anime and Javascript 

I don't like java

 

salamaashoush@gmail.com

@salamaashoush

Once upon a time ...

A poor web developer wants to make a three request in parallel with javascript, he thinks a little and said to himself well it is very easy, i can spawn three threads like I do every day with java

he opened google and start typing how to spawn three threads with JavaScript and .....

he started to wonder how things work in the browser with a single thread?

Dom Events ????

Ajax????

etc???

and after a long day of searching and reading, he finally understood that ...

Js has non-blocking io async model with event loop??

https://vimeo.com/96425312

he said, now I can do the job, and he opened google and searched for "how to write async code in js"?

and he found ...

7 Async Patterns

Agenda

  • Concurrency
  • Callbacks
  • Thunks
  • Promises
  • Generators
  • Aysnc await
  • Observable *
  • CSP *

Callbacks

function fakeAjax(param1,param2,..,cb){
 ....
    cb('result');
}

Implementation

let req1 = fakeAjax('/fake',(result)=>{
    console.log(result);
});

Usage

Pyramid of doom/Callback Hell

 

setTimeout(()=>{
    console.log('one');
    setTimeout(()=>{
         console.log('two');
         setTimeout(()=>{
              console.log('three');
        },1000)
    },1000)
},1000)
// continuation passing style
function one(cb){
    console.log('one');
    setTimeout(cb,1000);
}
function two(cb){
    console.log('two');
    setTimeout(cb,1000);
}
function three(){
    console.log('three')
}
one(()=>two(three()));

Inversion of control

trackCheckout(purchaseInfo,()=>{
    chargeCreditCard(purchaseInfo);
    showThankyouPage();
});
let called = false;
trackCheckout(purchaseInfo,()=>{
    if(!called){
        called = true;
        chargeCreditCard(purchaseInfo);
        showThankyouPage();
    }
});

Trust:

  • not too early
  • not too late
  • not too many times
  • not too few times
  • no lost context
  • no swallowed errors
  • .....

You have to fix all of them

callback shapes

function doSomething(ok,err){
    //...
    if(error){
        err(error);
    }else{
        ok(result);
    }
}

doSomething((result)=>{
    console.log(result);
},(error)=>{
    console.log(error);
})
function doSomething(cb){
    //...
    if(error){
        cb(error);
    }else{
        cb(null,result);
    }    
}

doSomething((error,result)=>{
    if(error){
      console.log(error);
    }else{
      return console.log(result);
    }
});

Separate callbacks:

Error first callbacks (Node):

3 parallel requests problem:

we need to make three requests in parallel and output them as soon as possible in order

"file1","file2","file3"

function fakeAjax(url,cb) {
	let fake_responses = {
		"file1": "The first text",
		"file2": "The middle text",
		"file3": "The last text"
	};
	let randomDelay = (Math.round(Math.random() * 1E4) % 8000) + 1000;

	console.log("Requesting: " + url);

	setTimeout(function(){
		cb(fake_responses[url]);
	},randomDelay);
}
// hold responses in whatever order they come back
let responses = {};
function getFile(file) {
	fakeAjax(file,(text) => {
		fileReceived(file,text);
	});
}
function fileReceived(file,text) {
	// haven't received this text yet?
	if (!responses[file]) {
		responses[file] = text;
	}
	let files = ["file1","file2","file3"];
	// loop through responses in order for rendering
	for (let i=0; i<files.length; i++) {
		// response received?
		if (files[i] in responses) {
			// response needs to be rendered?
			if (responses[files[i]] !== true) {
				console.log(responses[files[i]]);
				responses[files[i]] = true;
			}
		} else {
        		// can't render yet
			// not complete!
			return false;
		}
	}
	console.log("Complete!");
}
// request all files at once in "parallel"
getFile("file1");
getFile("file2");
getFile("file3");

Thunks

 a function that encapsulates synchronous or asynchronous code inside.

function add(x,y){
 return x + y;
}
let thunk = () => {
    add(5,6);
}
// always returns the same value 11
thunk();

sync thunk

function addAsync(x,y,cb){
 setTimeout(()=>cb(x+y),100);
}
let thunk = (cb) => {
    addAsync(5,6,cb);
}
// it only need a callback
// to do the work
thunk((sum)=>{
 console.log(sum);
});

async thunk

a container around  a particular collection of state, and you can use it everywhere to extract the same state 

 

time-independent ​wrapper around the value

Closures

+

thunks

 ===

power

// active thunk
function getFile(file) {
	let resp;

	fakeAjax(file,function(text){
		if (!resp) resp = text;
		else resp(text);
	});

	return  (cb) =>  {
		if (resp) cb(resp);
		else resp = cb;
	};
}

// request all files at once in "parallel"
let th1 = getFile("file1");
let th2 = getFile("file2");
let th3 = getFile("file3");

th1((text) => {
	console.log(text);
	th2((text) => {
		console.log(text);
		th3((text) => {
			console.log(text);
			console.log("Complete!");
		});
	});
});

thunks eliminated the when part of the equation

more info about callbacks and thunks

  • "Rethinking async programming in js" kyle course on frontend masters
  • "you don't know js (async/performance book)"

Promises

I promise you nothing

Restaurant metaphor​

again a time-independent wrapper around the value

sounds familiar 

Future values

"Completion/Error Events"

imagine that it returns an event listener and you listen to "complete" or "error" event

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

Implementation

getFile('file1').then((file)=>{
    console.log(file);
}).catch((error)=>{
    console.log(error);
}).finally(()=>{
    console.log('completed');
})

Usage

  • Pending, when the final value is not available yet. This is the only state that may transition to one of the other two states.
  • Fulfilled, when and if the final value becomes available. A fulfillment value becomes permanently associated with the promise. This may be any value, including undefined.
  • Rejected, if an error prevented the final value from being determined.  A rejection reason becomes permanently associated with the promise. This may be any value, including undefined, though it is generally an Error object, like in exception handling.
  • Settled, when the promise is completely finished his work

Promise status

Promises Guarantees

  • Callbacks will never be called before the completion of the current run of the JavaScript event loop.

  • Callbacks added with .then even after the success or failure of the asynchronous operation will be called

  • Multiple callbacks may be added by calling .then several times, to be executed independently in insertion order.

Promise Chaining

Chain then after catch

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
  console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);
doSomething()
.then(() => {
    throw new Error('Something failed');
    console.log('Do this');
})
.catch(() => {
    console.log('Do that');
})
.then(() => {
    console.log('Do this whatever happened before');
});

Composition

//shortcuts to manually create an already resolved or rejected promise respectively. This can be useful at times.
Promise.resolve();
Promise.reject();

//two composition tools for running asynchronous operations in parallel.
Promise.all([p1,p2]);
Promise.race([p1,p2]);

//Sequential composition is possible using some clever JavaScript:
[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());

 
function getFile(file) {
	return new Promise((resolve) => {
		fakeAjax(file,resolve);
	});
}
// Request all files at once in
// "parallel" via `getFile(..)`.
let p1 = getFile("file1");
let p2 = getFile("file2");
let p3 = getFile("file3");

// Render as each one finishes,
// but only once previous rendering
// is done.
p1
.then(console.log)
.then(() => p2)
.then(console.log)
.then(() => p3)
.then(console.log)
.then(() => console.log("Complete!"));

// or
["file1","file2","file3"]
.map(getFile)
.reduce((chain, p) => chain.then(()=>p).then(console.log), Promise.resolve());

Generators

the first time to understand generators

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

let g = gen(); // iterator object
g.next(); // {value: 1, done: false}
g.next(); // {value: 2, done: false}
g.next(); // {value: 3, done: false}
g.next(); // {value: undefined, done: true}

// for of loop
for(let i of g){
    console.log(value)
}

Run to completion semantics

the fact that whenever a function runs, it cannot be pre-empted and will run entirely before any other code runs

Generator is special

unlike normal function generator function can pause and continue execution in a different time and it i could run for ever without complete

function* idMaker() {
    var index = 0;
    while(true)
        yield index++;
}

let gen = idMaker(); // "Generator { }"

console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
// ... after many lines of code
//...
console.log(gen.next().value); // 3

Push

(Observer) --> producer in control

Dom Events

WebSockets

SereverSentEvents

NodeStreams

ServiceWorker

setInterval

 

Pull

(Iterator) --> consumer in controll

Arrays

etc

Generators let you write code looks like you push a value and it converts it to pull

https://www.youtube.com/watch?v=lil4YCCXRYc&t=1382s

function* logGenerator() {
  console.log(0);
  console.log(1, yield);
  console.log(2, yield);
  console.log(3, yield);
}

let gen = logGenerator();

gen.next();             // 0
gen.next('pretzel');    // 1 pretzel
gen.next('california'); // 2 california
gen.next('mayonnaise'); // 3 mayonnaise

Generator Object

  • Generator.next();
  • Generator.throw();
  • Generator.return();
// return will complete the generator
function* gen() {
    yield 1;
    return 5;
    yield 2; // unreachable code
}

Why does he mention this thing with async patterns?

Async Generators

function getData(v){
	setTimeout(()=>g.next(v),1000)
}
function * gen(){
    try{
      let x = yield getData(5);
      let y = x + (yield getData(6));
      console.log(y); // 11
    }catch(err){
        console.log(err)
    }
}
let g = gen();
g.next();

Generators + Promises

co(function *(){
  // resolve multiple promises in parallel
  var a = Promise.resolve(1);
  var b = Promise.resolve(2);
  var c = Promise.resolve(3);
  var res = yield [a, b, c];
  console.log(res);
  // => [1, 2, 3]
}).catch(onerror);

//find more here https://github.com/tj/co
function getFile(file) {
 	return new Promise(function(resolve){
 		fakeAjax(file,resolve);
 	});
}

co(function *loadFiles(){
	// Request all files at once in
	// "parallel" via `getFile(..)`.
	let p1 = getFile("file1");
	let p2 = getFile("file2");
	let p3 = getFile("file3");

	// Render as each one finishes,
	// but only once previous rendering
	// is done.
        try{
            console.log( yield p1 );
            console.log( yield p2 );
            console.log( yield p3 );
        }catch(error){
            console.log(error)
        }
	
})
.then(() => console.log("Complete!"))

When I finally understood the power of generators

Async Await

Beauty of writing sync-looking async code

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}


async function add1(x) {
    try{
     const a = await resolveAfter2Seconds(20);
     const b = await resolveAfter2Seconds(30);
     return x + a + b;
    }catch(err){
      console.log(err)
    }
 
}

add1(10).then(v => {
  console.log(v);  // prints 60 after 4 seconds.
})
function getFile(file) {
 	return new Promise((resolve) => {
 		fakeAjax(file,resolve);
 	});
}

(async () => {
	// Request all files at once in
	// "parallel" via `getFile(..)`.
	let p1 = getFile("file1");
	let p2 = getFile("file2");
	let p3 = getFile("file3");

	// Render as each one finishes,
	// but only once previous rendering
	// is done.
        try{
            console.log( await p1 );
            console.log( await p2 );
            console.log( await p3 );
        }catch(error){
            console.log(error);
        }
	
})()
.then(() => console.log("Complete!"))
// Asynchronous generator
async function* asyncGen() {
    yield 'a';
    yield 'b';
}
const iter = asyncGen()[Symbol.asyncIterator]();

iter.next().then(x => console.log(x));
    // { value: 'a', done: false }
iter.next().then(x => console.log(x));
    // { value: 'b', done: false }
iter.next().then(x => console.log(x));
    // { value: undefined, done: true }

Asynchronous iterables return asynchronous iterators, whose method next()returns Promises for {value, done} objects:

Asynchronous iterables

//sync for of loop with await inside async function
for (const x of await Promise.all([p1,p2,p3]));

// for await is exactly the same 
for await (const x of [p1,p2,p3]);

// in action
// note: you can use for-await-of inside async functions only
async function main() {
    const syncIterable = [
        Promise.resolve('a'),
        Promise.resolve('b'),
    ];
    for await (const x of syncIterable) {
        console.log(x);
    }
}
main();

for-await-of

async function* readLines(path) {
  let file = await fileOpen(path);

  try {
    while (!file.EOF) {
      yield await file.readLine();
    }
  } finally {
    await file.close();
  }
}

for await (const line of readLines(filePath)) {
  console.log(line);
}

Observables

Event streams

Push

observable pushes the value to the consumer

interface iterable {

    Generator iterator(void)

}

interface observable {

    void observer(Generator)

}

https://www.youtube.com/watch?v=lil4YCCXRYc&t=1382s

function listen(element, eventName) {
    return new Observable(observer => {
        // Create an event handler which sends data to the sink
        let handler = event => observer.next(event);

        // Attach the event handler
        element.addEventListener(eventName, handler, true);

        // Return a cleanup function which will cancel the event stream
        return () => {
            // Detach the event handler from the element
            element.removeEventListener(eventName, handler, true);
        };
    });
}
// Return an observable of special key down commands
function commandKeys(element) {
    let keyCommands = { "38": "up", "40": "down" };

    return listen(element, "keydown")
        .filter(event => event.keyCode in keyCommands)
        .map(event => keyCommands[event.keyCode])
}
//subscribe to the observable
let subscription = commandKeys(inputElement).subscribe({
    next(val) { console.log("Received key command: " + val) },
    error(err) { console.log("Received an error: " + err) },
    complete() { console.log("Stream complete") },
});
// After calling this function, no more events will be sent
subscription.unsubscribe();

Proposed Observable interface

//creates an Observable of the values provided as arguments
Observable.of("red", "green", "blue").subscribe({
    next(color) {
        console.log(color);
    }
});

/*
 > "red"
 > "green"
 > "blue"
*/
//Converting from an iterable to an Observable:
Observable.from(["mercury", "venus", "earth"]).subscribe({
    next(value) {
        console.log(value);
    }
});

/*
 > "mercury"
 > "venus"
 > "earth"
*/

https://github.com/tc39/proposal-observable

Working ES Observable implementations

observable proposal on stage1

CSP

Communicating sequential processes

CSP is a way to manage concurrency via channels

one of the reasons behind go-lang performance is its concurrency modal via channels

GOROUTINES

put and take values from channels is blocking by default

automatic back-pressure (streams concept)

let ch = chan();

function *process1(){
    yield put(ch,"Hello");
    let msg = yield take(ch);
    console.log(msg);
}

function *proccess2(){
    let greeting = yield take(ch);
    yield put(ch,`${greeting} world`);
    console.log("done!"); 
}

Channels concepts

The Process

The Process Can Pause

 Processes Wait For Values In Channels

Processes Communicate Through Channels

Channel Are Queues

http://lucasmreis.github.io/blog/quick-introduction-to-csp-in-javascript/

let ch = csp.chan();

csp.go(function*(){
   while(true){
     yield csp.put(ch, Math.random());
   }
});

csp.go(function*(){
   while(true){
     yield csp.take(csp.timeout(500));
     let num =  yield csp.take(ch);
     console.log(num);
   }
})

Js csp

https://github.com/ubolonton/js-csp

function fromEvent(el,eventType){
    let ch = csp.chan();
    document.addEventListener(el,(e)=>{
        csp.putAsync(ch,e)
    });
    return ch;
}

csp.go(function*(){
    let ch = fromEvent(el, 'mousemove');
    while(true){
        let event = yield csp.put(ch, Math.random());
        console.log(`${event.clientX}, event.clientY`);
    }
});

Event listener with channels

function* player(name, table) {
  while (true) {
    let 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* () {
  const 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();
});

Ping/Pong

Js Async programming is awesome

?