Tomasz Ducin
independent consultant, architect, developer, trainer, speaker #architecture #performance #javascript #typescript #react #angular
Tomasz Ducin
7th June 2019, Amsterdam
Tomasz Ducin
11th October 2017, Warsaw
Tomasz Ducin
10th October 2017, Warsaw
5 Architectures of Asynchronous JavaScript
5 Architectures of Asynchronous JavaScript
Tomasz Ducin
Tomasz Ducin
Tomasz Ducin
13th July 2017, London
5 Architectures of Asynchronous JavaScript
conference on JavaScript, Node & Internet of Things
conference on JavaScript, Node & Internet of Things
Tomasz Ducin
13th July 2017, London
5 Architectures of Asynchronous JavaScript
Tomasz Ducin
22nd June 2017, Kraków
Tomasz Ducin
3rd April 2017, Warsaw
Tomasz Ducin
30th March 2017, Warsaw
independent consultant
trainer @ Bottega IT Minds
run to completion
event loop
list.forEach(function(item){
...
});
setTimeout(function(){
...
}, delay);
var list = [1,2,3];
console.log("before");
list.forEach(function(item){
console.log(item);
});
console.log("after");
console.log("before");
setTimeout(function(){
console.log("inside");
}, 0);
console.log("after");
setTimeout(function(){
...
}, delay);
......................
...........function(){
console.log("inside");
}.....
.....................
(function() {
console.log(1);
setTimeout(function(){console.log(2)}, 1000);
setTimeout(function(){console.log(3)}, 0);
console.log(4);
})();
console.log(5);
(function() {
console.log(1);
setTimeout(.........................., 1000);
setTimeout(.........................., 0);
console.log(4);
})();
console.log(5);
.............
...............
.............................................
...........function(){console.log(3)}.....
...............
.....
...............
.............
...............
...........function(){console.log(2)}........
..........................................
...............
.....
...............
$(document).ready(function() {
$('.pull-me').click(function() {
$('.panel').slideToggle('slow');
});
});
var customers;
// usually takes 0,5s
ajax.get('customers', {
success: function(data){
customers = data;
}
});
var customers, products;
// usually takes 0,5s
ajax.get('customers', {
success: function(data){
customers = data;
}
});
// usually takes 2s
ajax.get('products', {
success: function(data){
doSomething(customers, data);
}
});
connection.query('CREATE DATABASE IF NOT EXISTS test', function (err) {
if (err) throw err;
connection.query('USE test', function (err) {
if (err) throw err;
connection.query('CREATE TABLE IF NOT EXISTS users('
+ 'id INT NOT NULL AUTO_INCREMENT,'
+ 'PRIMARY KEY(id),'
+ 'name VARCHAR(30)'
+ ')', function (err) {
});
});
});
connection.query('CREATE DATABASE IF NOT EXISTS test', function (err) {
});
connection.query('CREATE DATABASE IF NOT EXISTS test', function (err) {
if (err) throw err;
connection.query('USE test', function (err) {
});
});
connection.query('CREATE DATABASE IF NOT EXISTS test', function (err) {
if (err) throw err;
connection.query('USE test', function (err) {
if (err) throw err;
connection.query('CREATE TABLE IF NOT EXISTS users('
+ 'id INT NOT NULL AUTO_INCREMENT,'
+ 'PRIMARY KEY(id),'
+ 'name VARCHAR(30)'
+ ')', function (err) {
if (err) throw err;
});
});
});
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})
fetchData()
.then(callbackFn)
.catch(errorHandlerFn)
myCustomOperation()
.then(callbackFn)
.catch(errorHandlerFn)
pending
fulfilled
rejected
initial state,
not settled yet
operation has failed
operation has completed successfully
settled
return asyncOp1();
function promiseChain(){
return asyncOp1()
.then(asyncOp2)
.then(asyncOp3)
.then(asyncOp4);
}
return asyncOp1()
.then(asyncOp2);
return asyncOp1()
.then(asyncOp2)
.then(asyncOp3);
return asyncOp1()
.then(asyncOp2)
.then(asyncOp3)
.then(asyncOp4);
function sequential(){
return asyncOp1()
.then(asyncOp2)
.then(asyncOp3)
.then(asyncOp4);
}
op1
op2
op3
op4
sequential()
.then(anotherAsync)
another
function parallel(){
var p1 = asyncOp1();
var p2 = asyncOp2();
var p3 = asyncOp3();
var p4 = asyncOp4();
return Promise.all(
[p1, p2, p3, p4]);
}
op1
another
.all
parallel()
.then(anotherAsync)
op2
op3
op4
Promise.all([p1, p2, p3, p4])
.then( ([v1, v2, v3, v4]) => {...})
.catch( reason => {...})
Promise.race([p1, p2, p3, p4])
.then( value => {...})
.catch( reason => {...})
Promise.any([p1, p2, p3, p4])
.then( value => {...})
.catch( ([r1, r2, r3, r4]) => {...})
all
race
any
Promise.some([p1, p2, p3, p4], 2)
.then( (v1, v2) => {...})
.catch( ([r1, r2, r3]) => {...})
some
function zeroOneTwo(){
return [0, 1, 2];
}
function* zeroOneTwo(){
yield 0;
yield 1;
yield 2;
}
for (var i of zeroOneTwo()) {
console.log(i);
} // 1 2 3
function* generator(){
console.log(1, "inside");
yield "A";
console.log(2, "inside");
yield "B";
}
var iterator = generator();
console.log(1, "outside");
iterator.next();
console.log(2, "outside");
iterator.next();
console.log(3, "outside");
1: outside
1: inside
2: outside
2: inside
3: outside
function* generate(){
console.log(1, "inside");
let recv = yield "A";
console.log(2, "inside", recv);
yield "B";
}
var iter = generate();
console.log(1, "outside");
let item = iter.next();
console.log(2, "outside", item.value);
iter.next('hey!');
console.log(3, "outside");
1: outside
1: inside
2: outside, A
2: inside, hey!
3: outside
function* sequential(){
var v1 = yield asyncOp1();
var v2 = yield asyncOp2(v1);
var v3 = yield asyncOp3(v2);
return asyncOp4(v3);
}
let asyncSequential
= async(sequential);
asyncSequential()
.then(anotherAsync)
op1
op2
op3
op4
another
function* parallel(){
var p1 = asyncOp1();
var p2 = asyncOp2();
var p3 = asyncOp3();
var p4 = asyncOp4();
return yield p1 + yield p2
+ yield p3 + yield p4;
}
let asyncParallel
= async(parallel)
asyncParallel()
.then(anotherAsync)
op1
another
.all
op2
op3
op4
function async(makeGenerator){
return function () {
var generator = makeGenerator.apply(this, arguments);
function handle(result){
// result => { done: [Boolean], value: [Object] }
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value).then(function (res){
return handle(generator.next(res));
}, function (err){
return handle(generator.throw(err));
});
}
try {
return handle(generator.next());
} catch (ex) {
return Promise.reject(ex);
}
}
}
async function sequential(){
var v1 = await asyncOp1();
var v2 = await asyncOp2(v1);
var v3 = await asyncOp3(v2);
return asyncOp4(v3);
}
sequential()
.then(anotherAsync)
op1
op2
op3
op4
another
async function parallel(){
var p1 = asyncOp1();
var p2 = asyncOp2();
var p3 = asyncOp3();
var p4 = asyncOp4();
return await p1 + await p2
+ await p3 + await p4;
}
parallel()
.then(anotherAsync)
op1
another
.all
op2
op3
op4
async/await usecase
model {
beginTransfer(): Promise<TransferID> {
return HTTP.post('/transfers');
}
}
model {
beginTransfer(): Promise<TransferID> {
return HTTP.post('/transfers');
}
setTransferDetails(
transferID: TransferID,
details: TransferDetails
): Promise<void> {
return HTTP.post(`/transfers/${transferID}`, details);
}
}
model {
beginTransfer(): Promise<TransferID> {
return HTTP.post('/transfers');
}
setTransferDetails(
transferID: TransferID,
details: TransferDetails
): Promise<void> {
return HTTP.post(`/transfers/${transferID}`, details);
}
confirmTransfer(transferID: TransferID): Promise<?> {
return HTTP.post(`/transfers/${transferID}`, {action: "confirm"});
}
}
model {
}
async function scheduleTransfer(component){
let transferID = await model.beginTransfer();
}
async function scheduleTransfer(component){
let transferID = await model.beginTransfer();
// waiting for user to input data
await component.completedStep1();
await model.setTransferDetails(transferID, details);
// waiting for user confirmation
await component.completedStep2();
await model.confirmTransfer(transferID);
// waiting for user closing popup
await component.completedStep3();
return transferID;
}
async function scheduleTransfer(component){
let transferID = await model.beginTransfer();
// waiting for user to input data
await component.completedStep1();
}
async function scheduleTransfer(component){
let transferID = await model.beginTransfer();
// waiting for user to input data
await component.completedStep1();
await model.setTransferDetails(transferID, details);
}
async function scheduleTransfer(component){
let transferID = await model.beginTransfer();
// waiting for user to input data
await component.completedStep1();
await model.setTransferDetails(transferID, details);
// waiting for user confirmation
await component.completedStep2();
}
async function scheduleTransfer(component){
let transferID = await model.beginTransfer();
// waiting for user to input data
await component.completedStep1();
await model.setTransferDetails(transferID, details);
// waiting for user confirmation
await component.completedStep2();
await model.confirmTransfer(transferID);
}
async function scheduleTransfer(component){
let transferID = await model.beginTransfer();
// waiting for user to input data
await component.completedStep1();
await model.setTransferDetails(transferID, details);
// waiting for user confirmation
await component.completedStep2();
await model.confirmTransfer(transferID);
// waiting for user closing popup
await component.completedStep3();
}
function scheduleTransfer(component){
return model.beginTransfer()
.then(transferID => {
// waiting for user to input data
return component.completedStep1()
.then(() => model.setTransferDetails(transferID, details))
// waiting for user confirmation
.then(() => component.completedStep2())
.then(() => model.confirmTransfer(transferID))
.then(() => component.completedStep3())
.then(() => transferID);
})
}
function promiseChain(){
return asyncOp1()
.then(asyncOp2)
.then(asyncOp3)
.then(asyncOp4 ???);
}
using data from previous steps
async function sequential(){
var v1 = await asyncOp1();
var v2 = await asyncOp2(v1);
var v3 = await asyncOp3(v2);
return asyncOp4(v1, v2, v3);
}
same as in generators
async function renderChapters(urls) {
urls.map(getJSON)
.forEach(p => addToPage((await p).html));
}
async function renderChapters(urls) {
urls.map(getJSON)
.forEach(async p => addToPage((await p).html));
}
Syntax Error
parallel
(async x => x ** 2);
(async x => { return x ** 2; });
(async (x, y) => x ** y);
(async (x, y) => { return x ** y; });
const square = (async x => x ** 2);
square(5) // same as Promise.resolve(25)
square(5).then(console.log)
// output: 25
for await (const line of readLines(filePath)) {
console.log(line);
}
syncIterator.next()
// -> { value: ..., done: ... }
asyncIterator.next()
// -> Promise resolving with
// { value: ..., done: ... }
.then(({ value, done }) => /* ... */);
// all awaited functions return a promise
// all awaited functions return a promise
await delay(2000);
// all awaited functions return a promise
await delay(2000);
// webservices
const flowers = await fetch('flowers.jpg');
// all awaited functions return a promise
await delay(2000);
// webservices
const flowers = await fetch('flowers.jpg');
// I/O operations
let content = '...';
let fileName = 'filename.json';
await writeFile(fileName, content);
await doSomeProcessing(fileName);
Rich Harris, creator of Rollup
var data = await something();
// import gets blocked
import { m } from 'module';
m.run();
if (!Array.prototype.myMethod){
...
await something();
}
var data = await something();
export default data;
import {go, chan, take, put} from 'js-csp';
let chA = chan();
let chB = chan();
js-csp
yield put
yield take
chan
go
csp.go(function* () {
const rec1 = yield csp.take(chA);
console.log('A > RECEIVED:', rec1);
const sent2 = 'cat';
console.log('A > SENDING:', sent2);
yield csp.put(chB, sent2);
const rec3 = yield csp.take(chA);
console.log('A > RECEIVED:', rec3);
});
csp.go(function* () {
const sent1 = 'dog';
console.log('B > SENDING:', sent1);
yield csp.put(chA, sent1);
const rec2 = yield csp.take(chB);
console.log('B > RECEIVED:', rec2);
const sent3 = 'WAT!';
console.log('B > SENDING:', sent3);
yield csp.put(chA, sent3);
});
=> B > SENDING: dog
=> A > RECEIVED: dog
=> A > SENDING: cat
=> B > RECEIVED: cat
=> B > SENDING: WAT!
=> A > RECEIVED: WAT!
csp.go(function* () {
});
csp.go(function* () {
});
csp.go(function* () {
const rec1 = yield csp.take(chA);
});
csp.go(function* () {
const sent1 = 'dog';
console.log('B > SENDING:', sent1);
});
=> B > SENDING: dog
=> B > SENDING: dog
=> A > RECEIVED: dog
csp.go(function* () {
const rec1 = yield csp.take(chA);
console.log('A > RECEIVED:', rec1);
});
csp.go(function* () {
const rec1 = yield csp.take(chA);
console.log('A > RECEIVED:', rec1);
const sent2 = 'cat';
console.log('A > SENDING:', sent2);
});
csp.go(function* () {
const sent1 = 'dog';
console.log('B > SENDING:', sent1);
yield csp.put(chA, sent1);
const rec2 = yield csp.take(chB);
});
=> B > SENDING: dog
=> A > RECEIVED: dog
=> A > SENDING: cat
csp.go(function* () {
const rec1 = yield csp.take(chA);
console.log('A > RECEIVED:', rec1);
const sent2 = 'cat';
console.log('A > SENDING:', sent2);
yield csp.put(chB, sent2);
const rec3 = yield csp.take(chA);
});
csp.go(function* () {
const sent1 = 'dog';
console.log('B > SENDING:', sent1);
yield csp.put(chA, sent1);
const rec2 = yield csp.take(chB);
console.log('B > RECEIVED:', rec2);
});
=> B > SENDING: dog
=> A > RECEIVED: dog
=> A > SENDING: cat
=> B > RECEIVED: cat
csp.go(function* () {
const sent1 = 'dog';
console.log('B > SENDING:', sent1);
yield csp.put(chA, sent1);
const rec2 = yield csp.take(chB);
console.log('B > RECEIVED:', rec2);
const sent3 = 'WAT!';
console.log('B > SENDING:', sent3);
});
=> B > SENDING: dog
=> A > RECEIVED: dog
=> A > SENDING: cat
=> B > RECEIVED: cat
=> B > SENDING: WAT!
csp.go(function* () {
const sent1 = 'dog';
console.log('B > SENDING:', sent1);
yield csp.put(chA, sent1);
const rec2 = yield csp.take(chB);
console.log('B > RECEIVED:', rec2);
const sent3 = 'WAT!';
console.log('B > SENDING:', sent3);
yield csp.put(chA, sent3);
});
csp.go(function* () {
const rec1 = yield csp.take(chA);
console.log('A > RECEIVED:', rec1);
const sent2 = 'cat';
console.log('A > SENDING:', sent2);
yield csp.put(chB, sent2);
const rec3 = yield csp.take(chA);
console.log('A > RECEIVED:', rec3);
});
=> B > SENDING: dog
=> A > RECEIVED: dog
=> A > SENDING: cat
=> B > RECEIVED: cat
=> B > SENDING: WAT!
=> A > RECEIVED: WAT!
click
focus
mouse*
change
$(selector).on(event, function(e){
// use `this` and `e`
});
$someone.on('click', function(){
$element.trigger('custom');
});
$element.on('custom', function(){
// logic
});
Backbone.on(event, this.render);
initialize: function() {
this.listenTo(this.model,
'change', this.render);
}
completed
[1, 2, 3].map(e => e * 2)
.map(e => e * 2)
stream$.map(e => e * 2)
.subscribe(
value => console.log(`value: ${value}`),
...
);
map, filter, find, reduce, sort...
Reactive Streams
Functors/HOF
Arrays
items go down the stream
const click$ = Rx.Observable.fromEvent(document, 'click');
const click$ = Rx.Observable.fromEvent(document, 'click')
.map(e => ({
x: e.clientX,
y: e.clientY
}))
.filter(e => e.x < document.body.clientWidth/2);
click$.subscribe(
value => console.log(`value: ${value}`),
e => console.warn(`error: ${e}`),
() => console.log('completed')
);
getItems(onNext, onError) {
this.http
.get("/items")
.map(response => response.json())
.retry(2)
.subscribe(onNext, onError)
}
@Component({
selector: 'async-component',
template: `<code>promise|async</code>
<code>stream|async</code>`
})
export class AsyncComponent {
private promise: Promise<any>;
private stream: Observable<any>;
constructor(){
this.promise = new Promise((res, rej) => {
setTimeout(() => res('item-p'), 1000)
});
this.stream = Observable.of('item-s')
.delay(1000)
}
}
promise.then(onSuccess, onFailure)
promise.catch(onFailure)
observable$
.subscribe(onNext, onError, onCompleted)
async function process(args){
let user = await API.getUser(args);
let address = await API.getAddress(user);
return { user, address };
}
run to completion
event loop
By Tomasz Ducin
Abstract: In this talk we'll discuss 5 alternative approaches to handle async operations: callbacks, events, promises, coroutines and reactive streams. None of them is either legacy or a silver bullet - a good dev needs to pick the right tool for the job. However, in order to understand them, we must step back to fundamentals all these rely on: the mechanics of event loop and run to completion rule, as well as learn to distinguish between sync and async flow. Then we proceed to design patterns built on top of each of the 5 approaches, discussing their strengths and limitations. Funfacts, such as famous Promise.race() included!
independent consultant, architect, developer, trainer, speaker #architecture #performance #javascript #typescript #react #angular