Search code examples
jquerycallbackcoffeescriptpromisejquery-deferred

Chaining jQuery promises through callbacks


I've found myself inventing a pattern using jQuery promises and wondered to myself if it already exists somewhere else.

Basically, I have a chain of asynchronous actions that need to occur one after the other - but they're not necessarily all the same action, the actions may be conditional.

It might look like this (CoffeeScript syntax for brevity):

a = itemA.save()
a.done ->
  b = itemB.fetch()
  b.done ->
    if x
      c = itemC.fetch()
      c.done ->
        ui.showDone()
    else
      ui.showDone()

Note the ugliness:

  • Excessive nesting
  • Repeated code

My composition routine reduces the nesting. In use, it looks like this:

chain = itemA.save()
chain = andThen chain, ->
  itemB.fetch()
chain = andThen chain, ->
  if x
    itemC.fetch()
chain.always ->
  ui.showDone()

Where andThen looks like this:

andThen = (promise, doneCallback) ->
  result = $.Deferred()
  promise.fail (value) ->
    result.reject(value)
  promise.done (value) ->
    cbResult = doneCallback(value)
    if cbResult && cbResult['done']
      cbResult.done (value) ->
        result.resolve(value)
      cbResult.fail (value) ->
        result.reject(value)
    else
      result.resolve(cbResult)
  result.promise()

I'm probably reinventing the wheel. But I haven't found this wheel anywhere else. Where is it?

I'm primarily using promises in order to abstract out things like showing error messages, disabling buttons while requests are in flight, etc. Rather than embedding these things inline, using promises I'm able to hand off the promise to a component that is able to react to the success or failure of an ongoing action. It makes UI more consistent and composable.

This means I need a promise that represents the whole chain of multiple deferreds - and that will be resolved no matter what path is taken.


Solution

  • I'm probably reinventing the wheel. But I haven't found this wheel anywhere else. Where is it?

    It even has a similar name: the .then() method! It's the main idea of promises actually.

    itemA.save().then ->
      itemB.fetch()
    .then ->
      if x
        itemC.fetch()
    .always ->
      ui.showDone()