summaryrefslogtreecommitdiff
path: root/addons/web/doc/async.rst
diff options
context:
space:
mode:
authorAntony Lesuisse <al@openerp.com>2012-10-06 19:10:53 +0200
committerAntony Lesuisse <al@openerp.com>2012-10-06 19:10:53 +0200
commitbc3cf0b74ff033d247a826ba55b608922454548a (patch)
tree50ea97df74a07e3921ce13238abb5a9b1f107470 /addons/web/doc/async.rst
parent55bcd978af814c90b1d486055940f242dfe0dbde (diff)
rm files, move postload hook
bzr revid: al@openerp.com-20121006171053-qggp1r5263h7v7vd
Diffstat (limited to 'addons/web/doc/async.rst')
-rw-r--r--addons/web/doc/async.rst353
1 files changed, 353 insertions, 0 deletions
diff --git a/addons/web/doc/async.rst b/addons/web/doc/async.rst
new file mode 100644
index 00000000000..23b3409bd8f
--- /dev/null
+++ b/addons/web/doc/async.rst
@@ -0,0 +1,353 @@
+Don't stop the world now: asynchronous development and Javascript
+=================================================================
+
+As a language (and runtime), javascript is fundamentally
+single-threaded. This means any blocking request or computation will
+blocks the whole page (and, in older browsers, the software itself
+even preventing users from switching to an other tab): a javascript
+environment can be seen as an event-based runloop where application
+developers have no control over the runloop itself.
+
+As a result, performing long-running synchronous network requests or
+other types of complex and expensive accesses is frowned upon and
+asynchronous APIs are used instead.
+
+Asynchronous code rarely comes naturally, especially for developers
+used to synchronous server-side code (in Python, Java or C#) where the
+code will just block until the deed is gone. This is increased further
+when asynchronous programming is not a first-class concept and is
+instead implemented on top of callbacks-based programming, which is
+the case in javascript.
+
+The goal of this guide is to provide some tools to deal with
+asynchronous systems, and warn against systematic issues or dangers.
+
+Deferreds
+---------
+
+Deferreds are a form of `promises`_. OpenERP Web currently uses
+`jQuery's deferred`_, but any `CommonJS Promises/A`_ implementation
+should work.
+
+The core idea of deferreds is that potentially asynchronous methods
+will return a :js:class:`Deferred` object instead of an arbitrary
+value or (most commonly) nothing.
+
+This object can then be used to track the end of the asynchronous
+operation by adding callbacks onto it, either success callbacks or
+error callbacks.
+
+A great advantage of deferreds over simply passing callback functions
+directly to asynchronous methods is the ability to :ref:`compose them
+<deferred-composition>`.
+
+Using deferreds
+~~~~~~~~~~~~~~~
+
+`CommonJS Promises/A`_ deferreds have only one method of importance:
+:js:func:`Deferred.then`. This method is used to attach new callbacks
+to the deferred object.
+
+* the first parameter attaches a success callback, called when the
+ deferred object is successfully resolved and provided with the
+ resolved value(s) for the asynchronous operation.
+
+* the second parameter attaches a failure callback, called when the
+ deferred object is rejected and provided with rejection values
+ (often some sort of error message).
+
+Callbacks attached to deferreds are never "lost": if a callback is
+attached to an already resolved or rejected deferred, the callback
+will be called (or ignored) immediately. A deferred is also only ever
+resolved or rejected once, and is either resolved or rejected: a given
+deferred can not call a single success callback twice, or call both a
+success and a failure callbacks.
+
+:js:func:`~Deferred.then` should be the method you'll use most often
+when interacting with deferred objects (and thus asynchronous APIs).
+
+Building deferreds
+~~~~~~~~~~~~~~~~~~
+
+After using asynchronous APIs may come the time to build them: for
+`mocks`_, to compose deferreds from multiple source in a complex
+manner, in order to let the current operations repaint the screen or
+give other events the time to unfold, ...
+
+This is easy using jQuery's deferred objects.
+
+.. note:: this section is an implementation detail of jQuery Deferred
+ objects, the creation of promises is not part of any
+ standard (even tentative) that I know of. If you are using
+ deferred objects which are not jQuery's, their API may (and
+ often will) be completely different.
+
+Deferreds are created by invoking their constructor [#]_ without any
+argument. This creates a :js:class:`Deferred` instance object with the
+following methods:
+
+:js:func:`Deferred.resolve`
+
+ As its name indicates, this method moves the deferred to the
+ "Resolved" state. It can be provided as many arguments as
+ necessary, these arguments will be provided to any pending success
+ callback.
+
+:js:func:`Deferred.reject`
+
+ Similar to :js:func:`~Deferred.resolve`, but moves the deferred to
+ the "Rejected" state and calls pending failure handlers.
+
+:js:func:`Deferred.promise`
+
+ Creates a readonly view of the deferred object. It is generally a
+ good idea to return a promise view of the deferred to prevent
+ callers from resolving or rejecting the deferred in your stead.
+
+:js:func:`~Deferred.reject` and :js:func:`~Deferred.resolve` are used
+to inform callers that the asynchronous operation has failed (or
+succeeded). These methods should simply be called when the
+asynchronous operation has ended, to notify anybody interested in its
+result(s).
+
+.. _deferred-composition:
+
+Composing deferreds
+~~~~~~~~~~~~~~~~~~~
+
+What we've seen so far is pretty nice, but mostly doable by passing
+functions to other functions (well adding functions post-facto would
+probably be a chore... still, doable).
+
+Deferreds truly shine when code needs to compose asynchronous
+operations in some way or other, as they can be used as a basis for
+such composition.
+
+There are two main forms of compositions over deferred: multiplexing
+and piping/cascading.
+
+Deferred multiplexing
+`````````````````````
+
+The most common reason for multiplexing deferred is simply performing
+2+ asynchronous operations and wanting to wait until all of them are
+done before moving on (and executing more stuff).
+
+The jQuery multiplexing function for promises is :js:func:`when`.
+
+.. note:: the multiplexing behavior of jQuery's :js:func:`when` is an
+ (incompatible, mostly) extension of the behavior defined in
+ `CommonJS Promises/B`_.
+
+This function can take any number of promises [#]_ and will return a
+promise.
+
+This returned promise will be resolved when *all* multiplexed promises
+are resolved, and will be rejected as soon as one of the multiplexed
+promises is rejected (it behaves like Python's ``all()``, but with
+promise objects instead of boolean-ish).
+
+The resolved values of the various promises multiplexed via
+:js:func:`when` are mapped to the arguments of :js:func:`when`'s
+success callback, if they are needed. The resolved values of a promise
+are at the same index in the callback's arguments as the promise in
+the :js:func:`when` call so you will have:
+
+.. code-block:: javascript
+
+ $.when(p0, p1, p2, p3).then(
+ function (results0, results1, results2, results3) {
+ // code
+ });
+
+.. warning::
+
+ in a normal mapping, each parameter to the callback would be an
+ array: each promise is conceptually resolved with an array of 0..n
+ values and these values are passed to :js:func:`when`'s
+ callback. But jQuery treats deferreds resolving a single value
+ specially, and "unwraps" that value.
+
+ For instance, in the code block above if the index of each promise
+ is the number of values it resolves (0 to 3), ``results0`` is an
+ empty array, ``results2`` is an array of 2 elements (a pair) but
+ ``results1`` is the actual value resolved by ``p1``, not an array.
+
+Deferred chaining
+`````````````````
+
+A second useful composition is starting an asynchronous operation as
+the result of an other asynchronous operation, and wanting the result
+of both: :js:func:`Deferred.then` returns the deferred on which it was
+called, so handle e.g. OpenERP's search/read sequence with this would
+require something along the lines of:
+
+.. code-block:: javascript
+
+ var result = $.Deferred();
+ Model.search(condition).then(function (ids) {
+ Model.read(ids, fields).then(function (records) {
+ result.resolve(records);
+ });
+ });
+ return result.promise();
+
+While it doesn't look too bad for trivial code, this quickly gets
+unwieldy.
+
+Instead, jQuery provides a tool to handle this kind of chains:
+:js:func:`Deferred.pipe`.
+
+:js:func:`~Deferred.pipe` has the same signature as
+:js:func:`~Deferred.then` and could be used in the same manner
+provided its return value was not used.
+
+It differs from :js:func:`~Deferred.then` in two ways: it returns a
+new promise object, not the one it was called with, and the return
+values of the callbacks is actually important to it: whichever
+callback is called,
+
+* If the callback is not set (not provided or left to null), the
+ resolution or rejection value(s) is simply forwarded to
+ :js:func:`~Deferred.pipe`'s promise (it's essentially a noop)
+
+* If the callback is set and does not return an observable object (a
+ deferred or a promise), the value it returns (``undefined`` if it
+ does not return anything) will replace the value it was given, e.g.
+
+ .. code-block:: javascript
+
+ promise.pipe(function () {
+ console.log('called');
+ });
+
+ will resolve with the sole value ``undefined``.
+
+* If the callback is set and returns an observable object, that object
+ will be the actual resolution (and result) of the pipe. This means a
+ resolved promise from the failure callback will resolve the pipe,
+ and a failure promise from the success callback will reject the
+ pipe.
+
+ This provides an easy way to chain operation successes, and the
+ previous piece of code can now be rewritten:
+
+ .. code-block:: javascript
+
+ return Model.search(condition).pipe(function (ids) {
+ return Model.read(ids, fields);
+ });
+
+ the result of the whole expression will encode failure if either
+ ``search`` or ``read`` fails (with the right rejection values), and
+ will be resolved with ``read``'s resolution values if the chain
+ executes correctly.
+
+:js:func:`~Deferred.pipe` is also useful to adapt third-party
+promise-based APIs, in order to filter their resolution value counts
+for instance (to take advantage of :js:func:`when` 's special treatment
+of single-value promises).
+
+jQuery.Deferred API
+~~~~~~~~~~~~~~~~~~~
+
+.. js:function:: when(deferreds…)
+
+ :param deferreds: deferred objects to multiplex
+ :returns: a multiplexed deferred
+ :rtype: :js:class:`Deferred`
+
+.. js:class:: Deferred
+
+ .. js:function:: Deferred.then(doneCallback[, failCallback])
+
+ Attaches new callbacks to the resolution or rejection of the
+ deferred object. Callbacks are executed in the order they are
+ attached to the deferred.
+
+ To provide only a failure callback, pass ``null`` as the
+ ``doneCallback``, to provide only a success callback the
+ second argument can just be ignored (and not passed at all).
+
+ :param doneCallback: function called when the deferred is resolved
+ :type doneCallback: Function
+ :param failCallback: function called when the deferred is rejected
+ :type failCallback: Function
+ :returns: the deferred object on which it was called
+ :rtype: :js:class:`Deferred`
+
+ .. js:function:: Deferred.done(doneCallback)
+
+ Attaches a new success callback to the deferred, shortcut for
+ ``deferred.then(doneCallback)``.
+
+ This is a jQuery extension to `CommonJS Promises/A`_ providing
+ little value over calling :js:func:`~Deferred.then` directly,
+ it should be avoided.
+
+ :param doneCallback: function called when the deferred is resolved
+ :type doneCallback: Function
+ :returns: the deferred object on which it was called
+ :rtype: :js:class:`Deferred`
+
+ .. js:function:: Deferred.fail(failCallback)
+
+ Attaches a new failure callback to the deferred, shortcut for
+ ``deferred.then(null, failCallback)``.
+
+ A second jQuery extension to `Promises/A <CommonJS
+ Promises/A>`_. Although it provides more value than
+ :js:func:`~Deferred.done`, it still is not much and should be
+ avoided as well.
+
+ :param failCallback: function called when the deferred is rejected
+ :type failCallback: Function
+ :returns: the deferred object on which it was called
+ :rtype: :js:class:`Deferred`
+
+ .. js:function:: Deferred.promise()
+
+ Returns a read-only view of the deferred object, with all
+ mutators (resolve and reject) methods removed.
+
+ .. js:function:: Deferred.resolve(value…)
+
+ Called to resolve a deferred, any value provided will be
+ passed onto the success handlers of the deferred object.
+
+ Resolving a deferred which has already been resolved or
+ rejected has no effect.
+
+ .. js:function:: Deferred.reject(value…)
+
+ Called to reject (fail) a deferred, any value provided will be
+ passed onto the failure handler of the deferred object.
+
+ Rejecting a deferred which has already been resolved or
+ rejected has no effect.
+
+ .. js:function:: Deferred.pipe(doneFilter[, failFilter])
+
+ Filters the result of a deferred, able to transform a success
+ into failure and a failure into success, or to delay
+ resolution further.
+
+.. [#] or simply calling :js:class:`Deferred` as a function, the
+ result is the same
+
+.. [#] or not-promises, the `CommonJS Promises/B`_ role of
+ :js:func:`when` is to be able to treat values and promises
+ uniformly: :js:func:`when` will pass promises through directly,
+ but non-promise values and objects will be transformed into a
+ resolved promise (resolving themselves with the value itself).
+
+ jQuery's :js:func:`when` keeps this behavior making deferreds
+ easy to build from "static" values, or allowing defensive code
+ where expected promises are wrapped in :js:func:`when` just in
+ case.
+
+.. _promises: http://en.wikipedia.org/wiki/Promise_(programming)
+.. _jQuery's deferred: http://api.jquery.com/category/deferred-object/
+.. _CommonJS Promises/A: http://wiki.commonjs.org/wiki/Promises/A
+.. _CommonJS Promises/B: http://wiki.commonjs.org/wiki/Promises/B
+.. _mocks: http://en.wikipedia.org/wiki/Mock_object