Although jQuery Deferred promises aren’t A+ Compliant, I still find them to be very useful. Often when writing Backbone.Marionette apps I have needed to make a major change to a view and immediately after do some work. This happens in almost ever view in every app I have worked on. Say you need to navigate to another part of the app but first require closing the view the user is looking at. Calling the close or remove method on your view will often do the trick, but when you have extra work, timing becomes important. In many examples, I see people calling the event, and then executing the close. This does work, but is only working because of timing. If you use a jquery deferred you can execute the event after the closing of the view.
$deferred = $.Deferred() $deferred.resolve @close() $deferred.done -> App.vent.trigger 'show:dialog'
Image Loading with jQuery Promises
imageLoader = (url) -> loadImage = (deferred) -> loaded = -> unbindEvents() deferred.resolve image errored = -> unbindEvents() deferred.reject image unbindEvents = -> image.onload = null image.onerror = null image.onabort = null image = new Image() image.onload = loaded image.onerror = errored image.onabort = errored image.src = url # promise causes the resolve/reject methods to not be accessible. $.Deferred(loadImage).promise()
With the above imageLoader you could do something like the example below.
$promise = imageLoader 'http://catgif.com/cat.gif' $promise.done (image) -> $('.loading').replaceWith image $promise.fail -> $('.loading').replaceWith '<img src="sadface.png"></img>'
A promise is an eventual result of an asynchronous operation. Many people have argued over exactly what the terminology should be and how the inner workings should work. If you aren’t looking to argue about this, and just want to take advantage of promises. I recommend starting with q. Q is an A+ compliant library, and it works using the expected when then format.
# Normal Q Promise structure Q.fcall step1 .then step2 .then step3 .then (value) -> console.log value .catch (error) -> console.log error .done() # Any function that returns a promise promise = @climber() promise.when @climbAMountain() # A when is a static then promise.then @fallDown() .catch (error) -> # A note on Q promises: # It will catch any errors while you are climbing or falling .done() # Q also offers a try catch finally block promise = @anotherPromise() promise.then # try some work promise.catch # catch some error promise.fin # finally do thing
Q is a very flexible promise library. It allows you to chain promises and execute arrays of promises. It is easy to use, and combined with coffeescript becomes one of the most readable solutions.
Promises are great and have helped dig me out of a lot of holes. Having helpers to assist you along the way when building a web app makes development more enjoyable and relaxing. I’ll share some of the other tools that I use to help me along the way. Many people might be thinking why build my own when I can just download something like UnderscoreJS. Libraries are great, but occassionally you come up with a great piece that you just carry with you. I recommend building your own set and improving upon them. Share them with the developers you work with and just spread the word.
# Cleanup video and audio elements when you are done with them. cleanupMedia: ($media) -> return unless $media.length $media.pause() $media.src = '' $media.children('source').prop 'src', '' $media.remove().length = 0 # Methods to check for HTML5 Video and Audio Support supports_video: -> !!document.createElement('video').canPlayType supports_audio: -> !!document.createElement('audio').canPlayType # Move the cursor to the end of an input $.fn.focusToEnd = -> @each -> v = $(this).val() $(this).focus().val("").val v # Then do this: $('.input').focusToEnd() # A contains method for the String class if not String::contains then String::contains = -> String::indexOf.apply( this, arguments ) isnt -1 # Use it like so: "apple".contains 'app' # returns true