##JavaScript isn't threaded
- Synchronous code locks up the browser
- Asynchronous code frees up the browser but leads to...
##Async callback hell
"Return" values are no longer available to our synchronous code
$.get "/users",
success: (users) ->
# Are visits available yet?
$.get "/visits",
success: (visits) ->
# Are users available yet?
gotUsers = false, gotVisits = false;
successHandler = ->
if gotUsers && gotVisits
console.log("Fetched!")
errorHandler = -> console.error "Something bad happened"
$.get "/users",
success: ->
gotUsers = true
successHandler()
error: errorHandler
$.get "/visits",
success: ->
gotVisits = true
successHandler()
error: errorHandler
##We can "fix" this with nesting...
$.get "/users",
success: ->
$.get "/visits",
success: ->
console.log("Fetched!")
error: errorHandler
error: errorhandler
##... But we've traded one hell for another
$.get "/users"
success: (users) ->
for user in users
$.get "/users/#{user.id}/visits"
success: (visits) ->
$.get "/users/#{user.id}/visits/#{visit.id}/directions",
success: (directions) ->
$.get "/users/#{user.id}/visits/#{visit.id}/work_report
success: ->
console.log "Fetched!"
error: errorHandler
error: errorHandler
error: errorHandler
error: errorHandler
class Fetcher
fetch: (options) ->
@_fetchUsers
success: (users) ->
@_fetchVisits()
success: (visits) -> options.success?(users, visits)
error: ->
# Handle errors
options.error?()
_fetchUsers = (options) ->
$.get "/users"
success: (user) ->
@_usersFetched = true
options.success?(users)
error: (what) ->
options.error?(what)
_fetchVisits: (users, options) ->
$.get "/visits"
success: (visits) ->
@_visitsFetched = true
options.success?(visits)
error: (what) ->
options.error?(what)
new Fetcher().fetch
success: ->
# ...
It was easy to interrupt synchronous code and handle errors
try
users = $.get "/users"
visits = $.get "/visits"
@visitsCollection.find(...)...
# ...
user.save()
# ...
console.log "Fetched!"
catch error
console.error "Something bad happened!"
- Are objects returned by asynchronous functions
- Start out pending
- Become fulfilled or rejected at some point in the future
- Allow binding of callbacks to fulfillment/rejection events
- Have a then method
- Are already provided by AJAX calls
- Use
.done
and.fail
to handle fulfillment/rejection events - Can be used in custom code via
$.Deferred()
(more later)
Instead of callbacks going in, promises come out
userPromise = $.get "/users" # userPromise is "pending"
userPromise.done (response) ->
console.log "userPromise was resolved!"
userPromise.fail (error) ->
console.log "userPromise was rejected!"
$.get "/users"
.done (records) ->
console.log "Have users!"
.fail (error) ->
console.error "Something bad:", error
$.get("/users")
.done(function (records) {
console.log("Have users!")
).fail(function(error) {
console.error("Something bad:", error)
});
##Before:
$.get "/users",
success: (users) ->
$.get "/visits",
success: (visits) ->
console.log("Fetched!")
error: errorHandler
error: errorhandler
$.get "/users",
.done (users) ->
$.get "/visits",
.done (visits) ->
console.log("Fetched!")
.fail errorHandler
.fail errorhandler
The beauty of promises is that then chains together multiple promises...
promise1.then(promise2).then(promise3).then....
... bubbling success/failure state through the chain ...
promise1.then(promise2).then(promise3).then...
.done ->
console.log("All promises resolved!")
.fail ->
console.log("One promise was rejected!")
... jumping immediately to our error handler, just like exceptions
promise1.then ->
$("#loadingSpinner").show() # no promise
.then ->
doSomethingLater() # returns promise2
.then ->
doAnotherThingLater() # returns promise3
.then ->
console.log "Half done!" # no promise
.then ->
doAnotherThingAgain() # returns promise4
.then ->
console.log "Done!"
$("#loadingSpinner").hide() # no promise
.fail ->
console.log "promise1 OR promise2 OR promise3 OR promise4 failed!"
Pam.Loading.show() # Show spinner
$.get "/users" # success!
.then ->
$.get "/visits" # success!
.then ->
$.get "/404" # Error!
.then ->
$.get "/directions" # skipped
.then ->
console.log "Fetched!"
.fail ->
console.error "Failed!"
.always ->
Pam.Loading.hide()
-
accepts a success callback and failure callback
promise.then(successFn, failureFn)
-
invokes
successFn
on success, orfailureFn
on failure -
returns a promise, which is resolved with the value of the success/failure callbacks
promise2 = promise.then(successFn, failureFn)
-
if
successFn
orfailureFn
return a promise,promise2
becomes that promisepromise2 = promise.then -> $.get("/url")
-
promise2
is now (in effect) the promise returned by$.get
X, Y, Z are async functions returning promises
X().then =>
Y()
.then =>
Z()
.then =>
console.log "Success!"
.fail =>
console.error "X or Y or Z failed!"
X()
.then =>
Y()
.then =>
Z()
.then =>
console.log "Success!"
.fail =>
console.error "X or Y or Z failed!"
.always =>
console.log "I run regardless!"
class Fetcher
fetch: ->
# Promise "falls off" the end of the method
$.get("/users")
new Fetcher().fetch().done ->
console.log "Done!"
and are guaranteed to run in the correct order!
class Fetcher
fetch: ->
# Promise "falls off" the end of the method
$.get("/users").done (users) =>
# I run first!
@users = users
f = new Fetcher()
f.fetch().done ->
# I run second!
console.log "Users:", f.users
class Fetcher
fetch: (options) ->
@_fetchUsers
success: (users) =>
@_fetchVisits()
success: (visits) -> options.success?(users, visits)
error: ->
# Handle errors
options.error?()
_fetchUsers = (options) ->
$.get "/users"
success: (user) =>
@_usersFetched = true
options.success?(users)
error: (what) ->
options.error?(what)
_fetchVisits: (users, options) ->
$.get "/visits"
success: (visits) =>
@_visitsFetched = true
options.success?(visits)
error: (what) ->
options.error?(what)
new Fetcher().fetch
success: ->
# ...
class Fetcher
fetch: ->
@_fetchUsers().then =>
@_fetchVisits()
_fetchUsers = ->
$.get ("/users").done (users) =>
@_usersFetched = true
_fetchVisits: ->
$.get("/visits").done (visits) =>
@_visitsFetched = true
class Fetcher
fetch: ->
@_fetchUsers().then =>
@_fetchVisits()
.then =>
@_fetchPretzels()
_fetchUsers = ->
$.get ("/users").done (users) =>
@_usersFetched = true
_fetchVisits: ->
$.get("/visits").done (visits) =>
@_visitsFetched = true
_fetchPretzels: ->
$.get("/pretzels")
- a "private" interface, called a "deferred"
- exposes resolve/reject/done/fail
- a "public" interface, called a "promise"
- exposes only done/fail
- Created via
$.Deferred()
, calleddfd
by convention - Fulfilled internally by
resolve(...)
- Rejected internally by
reject(...)
- Watched publicly for success via
.done(callback)
- Watched publicly for failure via
.fail(callback)
- Provide
done
andfail
callbacks - Have the all-important
then
method - Prevent calling code from resolving/rejecting a promise which isn't theirs to manage
Your class/function makes a new deferred, returns its promise
myAsyncFunction = ->
dfd = $.Deferred()
# Resolve our deferred in 1 second
setTimeout (-> dfd.resolve("Time's up!")), 1000
# Return the "public" interface to our deferred
dfd.promise()
Your calling code receives a promise and waits for it to resolve:
myAsyncFunction()
.done (message) ->
alert(message) # Time's up!
.fail (exception) ->
alert("Something bad!")
console.error(exception)
myFunction: (url) ->
dfd = $.Deferred()
$.get url,
.done ->
# Churn through results and then...
dfd.resolve.apply(dfd, arguments)
.fail ->
dfd.reject.apply(dfd, arguments)
dfd.promise()
myFunction: (url) ->
$.get url,
.done ->
# Churn through results and then...
Bundles up several promises into a new promise:
$.when($.get("/users"), $.get("/visits")).then (users, visits) =>
console.log "Fetched users", users[0], "and visits", visits[0]
.fail =>
console.error "Failed!"
The new promises fails or succeeds based on the sub-promises
$.when(myFunction()).then =>
#...
myFunction().then =>
# ...
saveModels = ->
dfd = $.Deferred()
for model in myModels
model.save() # returns a promise
# ... ???
dfd.promise()
saveModels = ->
dfds = []
for model in myModels
dfds.push model.save() # returns a promise
$.when.apply(@, dfds).promise()
This is a quick presentation I did for my office, to get people excited about using Promises in our CoffeeScript.
Requires GistDeck.