KEMBAR78
Asynchronous programming with java script and node.js
Asynchronous Programming
with JavaScript and Node.js
Timur Shemsedinov
Software Architect at Metarhia, Lecturer at KPI
Metarhia
Asynchronous programming in JavaScript as of today
● callbacks
● async.js
● promises
● async/await
● ?
Asynchronous programming in JavaScript as of today
● callbacks
● async.js
● promises
● async/await
● generators/yield
● events
● functor + chaining + composition
Asynchronous programming in JavaScript as of today
● callbacks > async.js
● promises > async/await
● events
● functor + chaining + composition
Callbacks
(callback) => callback(data)
(...args, callback) => callback(err, data)
Use contracts: callback-last, error-first
You can implement hell easely
Callbacks
readConfig('myConfig', (e, data) => {
query('select * from cities', (e, data) => {
httpGet('http://kpi.ua', (e, data) => {
readFile('README.md', (e, data) => {
});
});
});
});
Callbacks
readConfig('myConfig',
query.bind(null, 'select * from cities',
httpGet.bind(null, 'http://kpi.ua',
readFile.bind('README.md', () => {
});
});
});
});
Callbacks
readConfig('myConfig');
function readConfig(fileName) {
...; query('select * from cities');
}
function query(statement) {
...; httpGet('http://kpi.ua');
}
...
Library async.js or analogues
async.method(
[... (data, cb) => cb(err, result) ...],
(err, result) => {}
);
Use callback-last, error-first
Define functions separately, descriptive names
Hell remains
Events
const ee = new EventEmitter();
const f1 = () => ee.emit('step2');
const f2 = () => ee.emit('step3');
const f3 = () => ee.emit('done');
ee.on('step1', f1.bind(null, par));
ee.on('step2', f2.bind(null, par));
ee.on('step3', f3.bind(null, par));
ee.on('done', () => console.log('done'));
ee.emit('step1');
Promise
new Promise((resolve, reject) => {
resolve(data);
reject(new Error(...));
})
.then(result => {}, reason => {})
.catch(err => {});
Separated control flow for success and fail
Hell remains for complex parallel/sequential code
Promise Sequential
Promise.resolve()
.then(readConfig.bind(null, 'myConfig'))
.then(query.bind(null, 'select * from cities'))
.then(httpGet.bind(null, 'http://kpi.ua'))
.catch((err) => console.log(err.message))
.then(readFile.bind(null, 'README.md'))
.catch((err) => console.log(err.message))
.then((data) => {
console.dir({ data });
});
Promise Parallel
Promise.all([
readConfig('myConfig'),
doQuery('select * from cities'),
httpGet('http://kpi.ua'),
readFile('README.md')
]).then((data) => {
console.log('Done');
console.dir({ data });
});
Promise Mixed: parallel / sequential
Promise.resolve()
.then(readConfig.bind(null, 'myConfig'))
.then(() => Promise.all([
query('select * from cities'),
gttpGet('http://kpi.ua')
]))
.then(readFile.bind(null, 'README.md'))
.then((data) => {
console.log('Done');
console.dir({ data });
});
async/await
async function f() {
return await new Promise(...);
}
f().then(console.log).catch(console.error);
Promises under the hood, Control-flow separated
Hell remains, Performance reduced
Functor + Chaining + composition
const c1 = chain()
.do(readConfig, 'myConfig')
.do(doQuery, 'select * from cities')
.do(httpGet, 'http://kpi.ua')
.do(readFile, 'README.md');
c1();
Functor + chaining + composition
function chain(prev = null) {
const cur = () => {
if (cur.prev) {
cur.prev.next = cur;
cur.prev();
} else {
cur.forward();
}
};
cur.prev = prev;
cur.fn = null;
cur.args = null;
...
...
cur.do = (fn, ...args) => {
cur.fn = fn;
cur.args = args;
return chain(cur);
};
cur.forward = () => {
if (cur.fn) cur.fn(cur.args, () => {
if (cur.next) cur.next.forward();
});
};
return cur;
}
Problems
of callbacks, async.js, Promise, async/await
● Nesting and syntax
● Different contracts
● Not cancellable, no timeouts
● Complexity and Performance
Tricks
Add timeout to any function
const fn = (par) => {
console.log('Function called, par: ' + par);
};
const fn100 = timeout(100, fn);
const fn200 = timeout(200, fn);
setTimeout(() => {
fn100('first'); fn200('second');
}, 150);
Add timeout to any function
function timeout(msec, fn) {
let timer = setTimeout(() => {
if (timer) console.log('Function timedout');
timer = null;
}, msec);
return (...args) => {
if (timer) {
timer = null;
fn(...args);
}
};
}
Make function cancelable
const fn = (par) => {
console.log('Function called, par: ' + par);
};
const f = cancelable(fn);
f('first');
f.cancel();
f('second');
Make function cancelable
const cancelable = (fn) => {
const wrapper = (...args) => {
if (fn) return fn(...args);
};
wrapper.cancel = () => {
fn = null;
};
return wrapper;
};
More wrappers
const f1 = timeout(1000, fn);
const f2 = cancelable(fn);
const f3 = once(fn);
const f4 = limit(10, fn);
const f5 = throttle(10, 1000, fn);
const f6 = debounce(1000, fn);
const f7 = utils(fn)
.limit(10)
.throttle(10, 100)
.timeout(1000);
Promisify and Callbackify
const promise = promisify(asyncFunction);
promise.then(...).catch(...);
const callback = callbackify(promise);
callback((err, value) => { ... });
Sync function to async
const f1 = par => par; const f2 = par => par;
const f3 = par => par; const f4 = par => par;
console.log(f4(f3(f2(f1('value')))));
const af1 = toAsync(f1); const af2 = toAsync(f2);
const af3 = toAsync(f3); const af4 = toAsync(f4);
af1('value', (e, data) => {
af2(data, (e, data) => {
af3(data, (e, data) => {
af4(data, (e, data) => {
console.log(data);
});
});
});
});
Sync function to async
const last = arr => arr[arr.length - 1];
const toAsync = fn => (...args) => {
const callback = last(args);
args.pop();
callback(null, fn(...args));
};
Sync function to Promise
const f1 = par => par; const f2 = par => par;
const f3 = par => par; const f4 = par => par;
console.log(f4(f3(f2(f1('value')))));
const pf1 = toPromise(f1); const pf2 = toPromise(f2);
const pf3 = toPromise(f3); const pf4 = toPromise(f4);
Promise.resolve()
.then(pf1.bind(null, 'value'))
.then(pf2())
.then(pf3())
.then(pf4())
.then((data) => {
console.log(data);
});
Sync function to Promise
const toPromise = fn => (...args) =>
new Promise(resolve => resolve(fn(...args)));
Convertors
● err-back to Promise
● Promise to err-back
● sync function to Promise
● sync function to err-back
● Events to Promise
● Promise to Events
● Events to err-back
● err-back to Events
Metasync
Metasync
● Function composition for asynchronous I/O
● Specific asynchronous abstractions
● Short and expressive syntax
● We use errback compatible contract
● IH
Function composition
inc = a => ++a;
square = a => a * a;
lg = x => log(10, x);
f = compose(inc, square, lg);
...but it’s synchronous
Function composition
Function composition is a great idea for asynchronous I/O
But there are questions:
● What about contracts?
○ for calls and callbacks, arguments and errors
○ timeouts, queueing, throttling
● How to add asynchronicity?
○ parallel and sequential
Asynchronous function composition
const readCfg = (name, cb) => fs.readFile(name, cb);
const netReq = (data, cb) => http.get(data.url, cb);
const dbReq = (query, cb) => db.select(query, cb);
const f1 = sequential(readCfg, netReq, dbReq);
const f2 = parallel(dbReq1, dbReq2, dbReq3);
// f1 & f2 contracts (...args, cb) => cb(err, data)
Flow commutation like in electronics
const fx = metasync.flow(
[f1, f2, f3, [[f4, f5, [f6, f7], f8]], f9]
);
Data collector
const dc1 = new metasync.DataCollector(4);
const dc2 = new metasync.DataCollector(4, 5000);
dc1.on('error', (err, key) => {});
dc2.on('timeout', (err, data) => {});
dc2.on('done', (errs, data) => {});
dc1.collect(data);
Key collector
const keyCollector = new KeyCollector(
['k1', 'k2'], (data) => console.dir(data)
);
keyCollector.collect('k1', {});
fs.readFile('HISTORY.md', (err, data) => {
keyCollector.collect('history', data);
});
Key collector
const kc = new metasync.KeyCollector(
['user', 'config', 'readme', 'timer'], (data) => console.dir(data)
);
kc.collect('user', { name: 'Marcus Aurelius' });
fs.readFile('HISTORY.md', (err,data) => kc.collect('history', data));
fs.readFile('README.md', (err,data) => kc.collect('readme', data));
setTimeout(
() => keyCollector.collect('timer', { date: new Date() }),
ASYNC_TIMEOUT
);
Collector
const dc1 = metasync
.collect(3)
.timeout(5000)
.done((err, data) => {});
dc1(item);
const dc2 = metasync
.collect(['key1', 'key2', 'key3'])
.timeout(5000)
.done((err, data) => {});
dc2(key, value);
Collector features
const dc = metasync
.collect(count)
.distinct()
.done((err, data) => {});
dc(key, error, value);
dc.pick(key, value);
dc.fail(key, error);
fs.readFile(filename, dc.bind(null, key));
dc.take(key, fs.readFile, filename);
Throttle
const t1 = metasync.throttle(5000, f);
t1();
t1();
t1(); // single call
setTimeout(t1, 7000); // another call
setTimeout(t1, 7100);
// will be fired at about 7000+5000
Queue
const cq = metasync.queue(3)
.wait(2000)
.timeout(5000)
.throttle(100, 1000)
.process((item, cb) => cb(err, result))
.success((item) => {})
.failure((item) => {})
.done(() => {})
.drain(() => {});
Timur Shemsedinov
tshemsedinov@github, timur.shemsedinov@gmail.com
tshemsedinov@facebook, marcusaurelius@habrahabr
Github repo: github.com/metarhia/metasync
http://how.programming.works
Telegram: t.me/metarhia & t.me/nodeua
Metarhia meetups: meetup.com/NodeUA,
meetup.com/HowProgrammingWorks
Metarhia

Asynchronous programming with java script and node.js

  • 1.
    Asynchronous Programming with JavaScriptand Node.js Timur Shemsedinov Software Architect at Metarhia, Lecturer at KPI
  • 2.
  • 3.
    Asynchronous programming inJavaScript as of today ● callbacks ● async.js ● promises ● async/await ● ?
  • 4.
    Asynchronous programming inJavaScript as of today ● callbacks ● async.js ● promises ● async/await ● generators/yield ● events ● functor + chaining + composition
  • 5.
    Asynchronous programming inJavaScript as of today ● callbacks > async.js ● promises > async/await ● events ● functor + chaining + composition
  • 6.
    Callbacks (callback) => callback(data) (...args,callback) => callback(err, data) Use contracts: callback-last, error-first You can implement hell easely
  • 7.
    Callbacks readConfig('myConfig', (e, data)=> { query('select * from cities', (e, data) => { httpGet('http://kpi.ua', (e, data) => { readFile('README.md', (e, data) => { }); }); }); });
  • 8.
    Callbacks readConfig('myConfig', query.bind(null, 'select *from cities', httpGet.bind(null, 'http://kpi.ua', readFile.bind('README.md', () => { }); }); }); });
  • 9.
    Callbacks readConfig('myConfig'); function readConfig(fileName) { ...;query('select * from cities'); } function query(statement) { ...; httpGet('http://kpi.ua'); } ...
  • 10.
    Library async.js oranalogues async.method( [... (data, cb) => cb(err, result) ...], (err, result) => {} ); Use callback-last, error-first Define functions separately, descriptive names Hell remains
  • 11.
    Events const ee =new EventEmitter(); const f1 = () => ee.emit('step2'); const f2 = () => ee.emit('step3'); const f3 = () => ee.emit('done'); ee.on('step1', f1.bind(null, par)); ee.on('step2', f2.bind(null, par)); ee.on('step3', f3.bind(null, par)); ee.on('done', () => console.log('done')); ee.emit('step1');
  • 12.
    Promise new Promise((resolve, reject)=> { resolve(data); reject(new Error(...)); }) .then(result => {}, reason => {}) .catch(err => {}); Separated control flow for success and fail Hell remains for complex parallel/sequential code
  • 13.
    Promise Sequential Promise.resolve() .then(readConfig.bind(null, 'myConfig')) .then(query.bind(null,'select * from cities')) .then(httpGet.bind(null, 'http://kpi.ua')) .catch((err) => console.log(err.message)) .then(readFile.bind(null, 'README.md')) .catch((err) => console.log(err.message)) .then((data) => { console.dir({ data }); });
  • 14.
    Promise Parallel Promise.all([ readConfig('myConfig'), doQuery('select *from cities'), httpGet('http://kpi.ua'), readFile('README.md') ]).then((data) => { console.log('Done'); console.dir({ data }); });
  • 15.
    Promise Mixed: parallel/ sequential Promise.resolve() .then(readConfig.bind(null, 'myConfig')) .then(() => Promise.all([ query('select * from cities'), gttpGet('http://kpi.ua') ])) .then(readFile.bind(null, 'README.md')) .then((data) => { console.log('Done'); console.dir({ data }); });
  • 16.
    async/await async function f(){ return await new Promise(...); } f().then(console.log).catch(console.error); Promises under the hood, Control-flow separated Hell remains, Performance reduced
  • 17.
    Functor + Chaining+ composition const c1 = chain() .do(readConfig, 'myConfig') .do(doQuery, 'select * from cities') .do(httpGet, 'http://kpi.ua') .do(readFile, 'README.md'); c1();
  • 18.
    Functor + chaining+ composition function chain(prev = null) { const cur = () => { if (cur.prev) { cur.prev.next = cur; cur.prev(); } else { cur.forward(); } }; cur.prev = prev; cur.fn = null; cur.args = null; ... ... cur.do = (fn, ...args) => { cur.fn = fn; cur.args = args; return chain(cur); }; cur.forward = () => { if (cur.fn) cur.fn(cur.args, () => { if (cur.next) cur.next.forward(); }); }; return cur; }
  • 19.
    Problems of callbacks, async.js,Promise, async/await ● Nesting and syntax ● Different contracts ● Not cancellable, no timeouts ● Complexity and Performance
  • 20.
  • 21.
    Add timeout toany function const fn = (par) => { console.log('Function called, par: ' + par); }; const fn100 = timeout(100, fn); const fn200 = timeout(200, fn); setTimeout(() => { fn100('first'); fn200('second'); }, 150);
  • 22.
    Add timeout toany function function timeout(msec, fn) { let timer = setTimeout(() => { if (timer) console.log('Function timedout'); timer = null; }, msec); return (...args) => { if (timer) { timer = null; fn(...args); } }; }
  • 23.
    Make function cancelable constfn = (par) => { console.log('Function called, par: ' + par); }; const f = cancelable(fn); f('first'); f.cancel(); f('second');
  • 24.
    Make function cancelable constcancelable = (fn) => { const wrapper = (...args) => { if (fn) return fn(...args); }; wrapper.cancel = () => { fn = null; }; return wrapper; };
  • 25.
    More wrappers const f1= timeout(1000, fn); const f2 = cancelable(fn); const f3 = once(fn); const f4 = limit(10, fn); const f5 = throttle(10, 1000, fn); const f6 = debounce(1000, fn); const f7 = utils(fn) .limit(10) .throttle(10, 100) .timeout(1000);
  • 26.
    Promisify and Callbackify constpromise = promisify(asyncFunction); promise.then(...).catch(...); const callback = callbackify(promise); callback((err, value) => { ... });
  • 27.
    Sync function toasync const f1 = par => par; const f2 = par => par; const f3 = par => par; const f4 = par => par; console.log(f4(f3(f2(f1('value'))))); const af1 = toAsync(f1); const af2 = toAsync(f2); const af3 = toAsync(f3); const af4 = toAsync(f4); af1('value', (e, data) => { af2(data, (e, data) => { af3(data, (e, data) => { af4(data, (e, data) => { console.log(data); }); }); }); });
  • 28.
    Sync function toasync const last = arr => arr[arr.length - 1]; const toAsync = fn => (...args) => { const callback = last(args); args.pop(); callback(null, fn(...args)); };
  • 29.
    Sync function toPromise const f1 = par => par; const f2 = par => par; const f3 = par => par; const f4 = par => par; console.log(f4(f3(f2(f1('value'))))); const pf1 = toPromise(f1); const pf2 = toPromise(f2); const pf3 = toPromise(f3); const pf4 = toPromise(f4); Promise.resolve() .then(pf1.bind(null, 'value')) .then(pf2()) .then(pf3()) .then(pf4()) .then((data) => { console.log(data); });
  • 30.
    Sync function toPromise const toPromise = fn => (...args) => new Promise(resolve => resolve(fn(...args)));
  • 31.
    Convertors ● err-back toPromise ● Promise to err-back ● sync function to Promise ● sync function to err-back ● Events to Promise ● Promise to Events ● Events to err-back ● err-back to Events
  • 32.
  • 33.
    Metasync ● Function compositionfor asynchronous I/O ● Specific asynchronous abstractions ● Short and expressive syntax ● We use errback compatible contract ● IH
  • 34.
    Function composition inc =a => ++a; square = a => a * a; lg = x => log(10, x); f = compose(inc, square, lg); ...but it’s synchronous
  • 35.
    Function composition Function compositionis a great idea for asynchronous I/O But there are questions: ● What about contracts? ○ for calls and callbacks, arguments and errors ○ timeouts, queueing, throttling ● How to add asynchronicity? ○ parallel and sequential
  • 36.
    Asynchronous function composition constreadCfg = (name, cb) => fs.readFile(name, cb); const netReq = (data, cb) => http.get(data.url, cb); const dbReq = (query, cb) => db.select(query, cb); const f1 = sequential(readCfg, netReq, dbReq); const f2 = parallel(dbReq1, dbReq2, dbReq3); // f1 & f2 contracts (...args, cb) => cb(err, data)
  • 37.
    Flow commutation likein electronics const fx = metasync.flow( [f1, f2, f3, [[f4, f5, [f6, f7], f8]], f9] );
  • 38.
    Data collector const dc1= new metasync.DataCollector(4); const dc2 = new metasync.DataCollector(4, 5000); dc1.on('error', (err, key) => {}); dc2.on('timeout', (err, data) => {}); dc2.on('done', (errs, data) => {}); dc1.collect(data);
  • 39.
    Key collector const keyCollector= new KeyCollector( ['k1', 'k2'], (data) => console.dir(data) ); keyCollector.collect('k1', {}); fs.readFile('HISTORY.md', (err, data) => { keyCollector.collect('history', data); });
  • 40.
    Key collector const kc= new metasync.KeyCollector( ['user', 'config', 'readme', 'timer'], (data) => console.dir(data) ); kc.collect('user', { name: 'Marcus Aurelius' }); fs.readFile('HISTORY.md', (err,data) => kc.collect('history', data)); fs.readFile('README.md', (err,data) => kc.collect('readme', data)); setTimeout( () => keyCollector.collect('timer', { date: new Date() }), ASYNC_TIMEOUT );
  • 41.
    Collector const dc1 =metasync .collect(3) .timeout(5000) .done((err, data) => {}); dc1(item); const dc2 = metasync .collect(['key1', 'key2', 'key3']) .timeout(5000) .done((err, data) => {}); dc2(key, value);
  • 42.
    Collector features const dc= metasync .collect(count) .distinct() .done((err, data) => {}); dc(key, error, value); dc.pick(key, value); dc.fail(key, error); fs.readFile(filename, dc.bind(null, key)); dc.take(key, fs.readFile, filename);
  • 44.
    Throttle const t1 =metasync.throttle(5000, f); t1(); t1(); t1(); // single call setTimeout(t1, 7000); // another call setTimeout(t1, 7100); // will be fired at about 7000+5000
  • 45.
    Queue const cq =metasync.queue(3) .wait(2000) .timeout(5000) .throttle(100, 1000) .process((item, cb) => cb(err, result)) .success((item) => {}) .failure((item) => {}) .done(() => {}) .drain(() => {});
  • 46.
    Timur Shemsedinov tshemsedinov@github, timur.shemsedinov@gmail.com tshemsedinov@facebook,marcusaurelius@habrahabr Github repo: github.com/metarhia/metasync http://how.programming.works Telegram: t.me/metarhia & t.me/nodeua Metarhia meetups: meetup.com/NodeUA, meetup.com/HowProgrammingWorks
  • 49.