Upload
mike-frey
View
256
Download
0
Tags:
Embed Size (px)
DESCRIPTION
Many developers new to Node.js struggle with writing asynchronous code in a clean, concise, easy to maintain manner. There are many strategies for managing and preventing 'callback hell' in Node.js. We'll walk through many of those strategies, from Promises to Generators to flow control libraries, and show that with a bit of forethought, writing asynchronous code in Javascript and Node.js can be easy and maintainable.
Citation preview
Promises, Generators &
Callbacks! Oh my!
Writing asynchronous code is hard
asynchronous code is difficult to read
asynchronous code is difficult to write
asynchronous code is difficult to maintain
Mike Frey
Why is writing asynchronous
code hard?
Composition
Patterns
Callbacks
Promises
Generators
Now entering Nerd War territory
Goals
Callbacks vs
Promises vs
Generators
Callbacks vs
Promises vs
Generators
xx
No Winner
Unbiased
Informative
Callbacks
How do they work?
Continuation Passing
Pass a function
to another function
Ask for work nowHandle result later
askForWork(function(err, res) { // handle result later})
Where are they used?
Everywhere
Node.js core
User-land
Even Promises
Benefits
Simple. Easy to use.
Prolific. Fast.
Problems
Error Handling
try{} catch(){}
try{} catch(){}
try { doWork(function(res) { // handle result })}catch (err) { // handle error }
!
doWork(function(err, res) { // handle error // handle result })!
!
Homework! read this:
!
joyent.com/developers/node/design/errors
ZA L GO Z
Ả L Ğ O !
asynchronous or
synchronous never both
Fix your API: process.nextTick()
setImmediate()
Fix their API: dezalgo
Callback Hell
Composition problem
NowLater
NowLaterLater-er
NowLaterLater-erLater-er-er
NowLaterLater-erLater-er-erLater-er-er-er
There’s nothing forcing you to write ten levels of nested callbacks, but the pattern does make it easy for you to do so.
- Raymond Julin (paraphrased)
NowLaterLater-erLater-er-erLater-er-er-er
NowLaterLater-erLater-er-erLater-er-er-er
getA(function() { getB(function() { getC(function() { // do something }) })})
function handleA() { getB(handleB)}function handleB() { getC(handleC)}function handleC() { // do something}getA(handleA)
async module
async.waterfall([ getA, getB, getC ], function(err, result) { // do something })
Callbacks !
Simple. Everywhere. Be careful.
Promises
How do they work?
Eventual Result
.then()
var promise = doSomething()promise.then( function (result) { // success callback }, function (error) { // error callback })
NowLater successLater failure
Where are they used?
jQuery AngularJS
Ember User-land
Chrome 32 Firefox 29 Opera 19
Node.js 0.11.13
Benefits
Composition: Chaining &
Error handling
.then()
.then()
.then()
NowLater successLater-er successLater-er-er successLater-er-er-er success
function addOne(num) { return new Promise( function(resolve, reject) { resolve(num+1) })}!
addOne(0) .then(addOne) .then(addOne) .then(addOne) .then(console.log)
function addOne(num) { return num+1}!
var p = new Promise( function(res, rej) { res(0) }) .then(addOne) .then(addOne) .then(addOne) .then(console.log)
Rejections bubble
Errors bubble
NowLater successLater-er successLater-er-er successAny failure
getSpeakers('MidwestJS') .then(getGithubUsers) .then(getPublicRepos) .then(listRepos, handleError)
Problems
Slow
Slow
Incompatible Proposals &
Implementations
jQuery vs
everyone else
Promises !
Composable. Eventual Result.
Generators
What are they?
How do they work?
function*
function* tick() {!
!
}!
!
!
!
function* tick() {!
!
}!
var itr = tick()!
!
function* tick() {!
!
}!
var itr = tick()itr.next()!
function* tick() { yield 42!
}!
var itr = tick()itr.next().value // 42!
yield
function* tick() { yield 42 yield 43}!
var itr = tick()itr.next().value // 42itr.next().value // 43itr.next().done // true
function* tick() { var x = yield var y = yield}!
var itr = tick()itr.next()itr.next(42) // x becomes 42itr.next(43) // y becomes 43
function* tick() { var num = 0 while (!(yield num++));}!
var itr = tick()itr.next().value // 0itr.next().value // 1itr.next().value // 2
function* tick() { var num = 0 while (!(yield num++));}!
var itr = tick()itr.next().value // 0itr.next().value // 1itr.next(true).done // true
Replacing callbacks
function delay(time, callback) { setTimeout(function() { callback('Slept for ' + time) }, time)}
function delayThings() { delay(1000, function(result1) { console.log(result1) delay(1200, function(result2) { console.log(result2) }) })}// Slept for 1000// Slept for 1200
function* delayThings() { var results1 = yield delay(1000) console.log(results1)!
var results2 = yield delay(1200) console.log(results2)}
function run(generator) { function resume(value) { itr.next(value) } var itr = generator(resume) itr.next()}
function* delayThings() { var results1 = yield delay(1000) console.log(results1)!
var results2 = yield delay(1200) console.log(results2)}!
function* delayThings(resume) { var results1 = yield delay(1000, resume) console.log(results1)!
var results2 = yield delay(1200, resume) console.log(results2)}!
run(delayThings)
More callbacks?
thunkify
// simplified from// https://github.com/visionmedia/node-thunkify
!function thunkify(fn){ return function(){ var args = Array.prototype.slice.call(arguments) return function(done){ args.push(function(){ done.apply(null, arguments) }) fn.apply(null, args) } }}
delay = thunkify(delay)!
var thunk = delay(1000)!
thunk(function(result) { console.log(result)})
function run(generator) { function resume(value) { itr.next(value) } var itr = generator(resume) itr.next()}!
function run(generator) { function resume(ret) { var obj = itr.next(ret) if (obj.done) return obj.value(resume) } var itr = generator() resume()}
function* delayThings() { var results1 = yield delay(1000) console.log(results1)!
var results2 = yield delay(1200) console.log(results2)}!
run(delayThings)
yield Now Later
yield Now yield Later Later-er
yield Now yield Later yield Later-er Later-er-er
yield Now yield Later yield Later-er yield Later-er-er Later-er-er-er …
co
var delayThings = co(function*() { var results1 = yield delay(1000) console.log(results1)!
var results2 = yield delay(1200) console.log(results2)})!
delayThings()
var delayThings = co(function*() { var delays = [delay(1000), delay(1200)] var results = yield delays!
console.log(results[0]) console.log(results[1])})!
delayThings()
Where are they used?
co koa
User-land
Firefox 31 Chrome (flag)
Node.js v0.11.10 (flag) Opera (flag)
Benefits
Feels synchronous
try{} catch(){}
New cool
Problems
Support
regenerator facebook.github.io/regenerator/
traceur github.com/google/traceur-compiler
Immature implementations
Generators !
yield statement. Pausable function*.
Built for iterators.
Writing asynchronous code is hard
But it doesn’t
have to be!
Call to action
Explore
Streams http://nodestreams.com
http://highlandjs.org
Thank you!
Questions? !
References available here: github.com/mikefrey/cpg-talk