Note:
When I write callback style I mean
Continuation-passing style
(error and value as callback parameters)
getUser 'john', (error, user) -> // ... getFeed user, (error, feed) -> // ...
Callback
A callback is a piece of executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at some convenient time.
Wikipedia, The Free Encyclopedia.
App.getData = (url, callback) ->
http.request url, (response) -> response.on 'data', (result) -> if result callback(null, result) else callback(new Error('No data!'))
Previous example
as theoretical synchronous code
App.getData = (url) ->
response = http.request(url) # block if result = response.getData() # block return result else throw new Error('No data!')
App.getData = (url, callback) ->
http.request url, (response) -> response.on 'data', (result) -> if result callback(null, result) else callback(new Error('No data!'))
Two examples side by side. What's new? What's missing?
App.getData = (url) ->
response = http.request(url) # block if result = response.getData() # block return result else throw new Error('No data!')
In callback style, there is
NO RETURN
App.getData = (url, callback) ->
http.request url, (response) -> response.on 'data', (result) -> if result # Nobody will receive it! return result else throw new Error('No data!')
In callback style, there is
NO THROW
App.getData = (url, callback) ->
http.request url, (response) -> response.on 'data', (result) -> if result return result else # Nobody will catch it! throw new Error('No data!')

From official docs. Crashes whole application.
fs.rename "/hello", "/world", (err) ->
if err
throw err # LOL
else
fs.stat "/world", (err, stats) ->
if err
throw err # LOL2
else
console.log "stats: " + stats

Previous solution: allow for one more callback
process.on 'uncaughtException', (err) ->
// Any last words? console.log('Exception: ' + err)
fs.rename "/hello", "/world", (err) ->
if err
throw err
else
fs.stat "/world", (err, stats) ->
if err
throw err
else
console.log "stats: " + stats

Current "solution". Crash one worker at the time.
fork = domain.create()fork.on 'error', (err) -> console.log('Exception: ' + err)fork.run -> fs.rename "/hello", "/world", (err) ->
if err
throw err
else
fs.stat "/world", (err, stats) ->
if err
throw err
else
console.log "stats: " + stats
In callback style, there are
NO GUARANTEES
-
callbacks can be called twice!
- Old Express.js bug
(connected with throwing Exceptions)
-
callbacks create side efects!
- Called callback can produce side effect
in unpredicatable way.
We quickly descent into
callback hell
var p_client = new Db('integration_tests_20');
p_client.open(function(err, p_client) {
p_client.dropDatabase(function(err, done) {
p_client.createCollection(function(err, collection) {
collection.insert({'a':1}, function(err, docs) {
collection.find({'_id': 12}, function(err, cursor) {
cursor.toArray(function(err, items) {
test.assertEquals(1, items.length);
p_client.close();
});
});
});
});
});
});
(Not as much, but you get the idea. Plus error handling)
All to all
- NO STACK (return/catch)
- NO GUARANTEES
- CALLBACK HELL
- BUT WE DON'T BLOCK!
is it worth it?
Promise is an object
behaving in certain way.
Promise/A+ is a standard
how exactly it should behave.
Promise
- was discovered circa 1989
- widely used outside JS
-
deals with callback hell (chaining callbacks)
-
re-introduces stack (return/throw)
-
provides guarantees about async code

Promises are objects
that represent a value
that may not be available yet
promise = getUser('john')promise.then (user) -> user.sayHi()
promise.then
(onFulfilled, onError)
the only official API of the Promise/A+ object™
// then :: Promise a -> (a -> b) -> Promise b
// then :: Promise a -> (a -> Promise b) -> Promise b
|
v
+-------------+
| |
+-------+ Pending +------+
| | | |
| +-------------+ |
| |
| Promise is a state machine |
v v
+-------------+ +--------------+
| | | |
+ Fulfilled + + Rejected +
| | | |
+-------------+ +--------------+
Rule #1
Instead of passing a callback
getUser 'john', (error, user) -> if error handleError(error) else render(user)
Call then on returned Promise
getUser('john') .then(render, handleError)
Rule #2
Instead of calling a callback
getUser = (username, callback) -> $.ajax url: '/' + username success: (data) -> callback(null, data) error: (reason) -> callback(reason)
Return a Promise
getUser = (username, callback) ->
new Promise (resolve, reject) -> $.ajax url: '/' + username success: resolve error: reject
Transforms
between synchronous and promise world
Case #1
Blocking functions in sequence
user = getUser('john')name = getData(user)feed = query(data)
becomes
getUser('john') .then (user) -> getData(user) .then (data) -> query(data)
Case #2
Throwing exceptions
user = getUser('john')throw new Error('No user!') if not user
becomes
getUser('john') .then (user) -> throw new Error('No user!') if not user
Case #3
Catching exceptions
try user = getUser('john')catch error handleError(error)
becomes
getUser('john') .then (user) -> user.name .then undefined, (error) -> # onFulfilled = undefined handleError(error)
Case #4
Re-throwing exceptions
try user = getUser('john') throw new Error('No user!') if not user
catch error throw new Error('Error: ' + error)
becomes
getUser('john') .then (user) -> user.name .then undefined, (error) -> throw new Error('Error: ' + error)
Transforms
between callback-passing style and Future world
Error handling
getUser 'john', (error, user) -> if error handleError(error) else getFeed user, (error, feed) -> if error handleError(error) else callback(feed)
becomes
getUser('john') .then (user) -> getFeed(name) .then (feed) -> callback(feed) .then undefined, handleError
Converting Promise to a
Callback-passing style function
callbackMeUser = (user, callback) ->
getUser(user).nodeify(callback)
Usage:
callbackMeUser 'john', (error, user) ->
if error handleError(error) else sayHi(user)
Converting to a Promise style
getUser = callbackMeUser.denodeify()
Usage:
getUser('john')
.then (user) -> sayHi(user) .then undefined, (error) -> handleError(error)
Running Promises in parallel
Callback is called only if all promises are fulfilled.
Q.spread:promises = [getUser('alice'), getUser('bob')]Q.spread promises, (alice, bob) -> engage(alice, bob)
Q.all:promises = [getUser('alice'), getUser('bob')]Q.all promises, (users) -> # passes array engage(users[0], users[1])
Executable specification!

Promises/A+ does not define:
-
How to instantiate Promise/A+ object
-
How to implement Promise/A+
-
Additional API on top of the basic one (then method)
- Anything about onProgress callback
28 compatible implementations
you can use any of them, they are all compatible

jQuery is not one of them :(
It has some pitfalls:
- before 1.8: then method doesn't even return a promise
- after 1.8: doesn't handle well edge cases (e.g. throw)
My humble recommendations
-
then/promise - bare bones API, few addons
-
RSVP - little more advanced, simple
-
Q - powerful, battle-tested, but strange API
-
when - powerful, nice and huge API
https://github.com/promises-aplus/promises-spec/blob/master/implementations.md
Promise/A
Promise/A+
Both are standards.
What is the difference?
Promise/A+
Clarify some edge cases
What if onFulfilled or onRejected returns a promise?
Answer:
Promise returned by then "becomes" that one
promise = Application.getUser() .then (user) -> return Twitter.fetchUserTweets()promise.then (userTweets) ->
display(userTweets)
// then :: Promise a -> (a -> b) -> Promise b
// then :: Promise a -> (a -> Promise b) -> Promise b
What if onFulfilled or onRejected returns a promise?
Answer:
Promise returned by then "becomes" that one
Application.getUser() .then -> (user) return Twitter.fetchUserTweets(user) .then (userTweets) -> # display them?
// then :: Promise a -> (a -> b) -> Promise b
// then :: Promise a -> (a -> Promise b) -> Promise b
What if onRejected
throws an exception?
Answer:
Exception is passed to the next onRejected handler
App.bogusGetUser() .then(getTweets, (e) -> conle.log(e)) .then(display, (e) -> console.log(e))
# => ReferenceError {}
What if promise called upon
is already fulfilled or rejected?
Answer?
onFulfilled and onRejected
should be called asynchronously
# Won't block:promise.then blockForever
And other interesting only
for library developers
- Omissions (free hand)
- Clarifications (bad implementations)
- Additions (most already covered)
- yada yada yada