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