Callbacks are a nice way of Iversion of control, but are even more powerful with deferred objects.
Basically callbacks are a way to pass back the control from a called function, the deferred/promise just do the opposite: it returns the control the called function.
Consider this scenario: a table widget, with the classic delete/modify buttons next to each row. Since the table widget is a generic component, we want to handle the delete operation on the model outside the widget, and we don’t event want to extend the generic object to do that, we want to user a callback:
In the example above: the callback first calls the backend to remove the record and then removes the row from the table (I mean the DOM). In order to do this: it needs to know something about the table widget (in this case, the method “remove_row”).
But what if we could pass the ball back to the widget? Let’s rewrite it with a deferred object:
It’s more linear but, most important, the callback knows nothing about the remove_that_damn_row_from_the_dom method of the widget, it just passed back the control, it’s like saying “I’m done, it’s your turn”.
More separation, less documentation to read, easier to implement, less errors.
On the widget side, the callback should be treated this way
The Deferred/Promise pattern is great for two reason:
So far so good, but we need to keep in mind that this pattern it’s just an abstraction and we should be always aware of where, in the code, our procedure is executed.
Consider this example where a popover is used to pick up some options: the whole process is handled with a deferred() object:
And the code to trigger the popover is
What is not clear here is that the code of the function very_long_operation() is actually executed within the handler of the .click(), after the .hide() operation but before the click handler is completed.
Here is the point: if the function very_long_operation() turns out to be a long process, say 1 or 2 secs, it will take this interval for the popover to disappear after the click, since the DOM will be updated only at the end of the event handler.
From the user perspective, this results in a less responsive user interface.
This is particulary bad since we want to decouple the when from then:
The smartest solution could be a WebWorker: refactor each lines of very_long_operation() in order to exploit HTML5, but this will not be available on oldest browsers.
The quickest solution is and old-quick-and-dirt setTimeout:
It just detach the long process very_long_operation() from the event loop, letting the click handler to complete (and update the DOM), and everything is safe: the user interface and our decoupled code.
The same code using Stackable:
PS: The same result could be achieved by wrapping with the timeout trick the line deferred.resolve(‘my_choice’); but this is wrong from the point of view of code decoupling: the popover code knows nothing (and doesn’t have to) about the code executed with the resolve() statement, putting the timeout here would imply that our deferred object always assumes to deal with very long process.
Deferreds is all about decoupling