[MERGE] callback2deferred session.rpc
[odoo/odoo.git] / doc / async.rst
1 Don't stop the world now: asynchronous development and Javascript
2 =================================================================
3
4 As a language (and runtime), javascript is fundamentally
5 single-threaded. This means any blocking request or computation will
6 blocks the whole page (and, in older browsers, the software itself
7 even preventing users from switching to an other tab): a javascript
8 environment can be seen as an event-based runloop where application
9 developers have no control over the runloop itself.
10
11 As a result, performing long-running synchronous network requests or
12 other types of complex and expensive accesses is frowned upon and
13 asynchronous APIs are used instead.
14
15 Asynchronous code rarely comes naturally, especially for developers
16 used to synchronous server-side code (in Python, Java or C#) where the
17 code will just block until the deed is gone. This is increased further
18 when asynchronous programming is not a first-class concept and is
19 instead implemented on top of callbacks-based programming, which is
20 the case in javascript.
21
22 The goal of this guide is to provide some tools to deal with
23 asynchronous systems, and warn against systematic issues or dangers.
24
25 Deferreds
26 ---------
27
28 Deferreds are a form of `promises`_. OpenERP Web currently uses
29 `jQuery's deferred`_, but any `CommonJS Promises/A`_ implementation
30 should work.
31
32 The core idea of deferreds is that potentially asynchronous methods
33 will return a :js:class:`Deferred` object instead of an arbitrary
34 value or (most commonly) nothing.
35
36 This object can then be used to track the end of the asynchronous
37 operation by adding callbacks onto it, either success callbacks or
38 error callbacks.
39
40 A great advantage of deferreds over simply passing callback functions
41 directly to asynchronous methods is the ability to :ref:`compose them
42 <deferred-composition>`.
43
44 Using deferreds
45 ~~~~~~~~~~~~~~~
46
47 `CommonJS Promises/A`_ deferreds have only one method of importance:
48 :js:func:`Deferred.then`. This method is used to attach new callbacks
49 to the deferred object.
50
51 * the first parameter attaches a success callback, called when the
52   deferred object is successfully resolved and provided with the
53   resolved value(s) for the asynchronous operation.
54
55 * the second parameter attaches a failure callback, called when the
56   deferred object is rejected and provided with rejection values
57   (often some sort of error message).
58
59 Callbacks attached to deferreds are never "lost": if a callback is
60 attached to an already resolved or rejected deferred, the callback
61 will be called (or ignored) immediately. A deferred is also only ever
62 resolved or rejected once, and is either resolved or rejected: a given
63 deferred can not call a single success callback twice, or call both a
64 success and a failure callbacks.
65
66 :js:func:`~Deferred.then` should be the method you'll use most often
67 when interacting with deferred objects (and thus asynchronous APIs).
68
69 Building deferreds
70 ~~~~~~~~~~~~~~~~~~
71
72 After using asynchronous APIs may come the time to build them: for
73 `mocks`_, to compose deferreds from multiple source in a complex
74 manner, in order to let the current operations repaint the screen or
75 give other events the time to unfold, ...
76
77 This is easy using jQuery's deferred objects.
78
79 .. note:: this section is an implementation detail of jQuery Deferred
80           objects, the creation of promises is not part of any
81           standard (even tentative) that I know of. If you are using
82           deferred objects which are not jQuery's, their API may (and
83           often will) be completely different.
84
85 Deferreds are created by invoking their constructor [#]_ without any
86 argument. This creates a :js:class:`Deferred` instance object with the
87 following methods:
88
89 :js:func:`Deferred.resolve`
90
91     As its name indicates, this method moves the deferred to the
92     "Resolved" state. It can be provided as many arguments as
93     necessary, these arguments will be provided to any pending success
94     callback.
95
96 :js:func:`Deferred.reject`
97
98     Similar to :js:func:`~Deferred.resolve`, but moves the deferred to
99     the "Rejected" state and calls pending failure handlers.
100
101 :js:func:`Deferred.promise`
102
103     Creates a readonly view of the deferred object. It is generally a
104     good idea to return a promise view of the deferred to prevent
105     callers from resolving or rejecting the deferred in your stead.
106
107 :js:func:`~Deferred.reject` and :js:func:`~Deferred.resolve` are used
108 to inform callers that the asynchronous operation has failed (or
109 succeeded). These methods should simply be called when the
110 asynchronous operation has ended, to notify anybody interested in its
111 result(s).
112
113 .. _deferred-composition:
114
115 Composing deferreds
116 ~~~~~~~~~~~~~~~~~~~
117
118 What we've seen so far is pretty nice, but mostly doable by passing
119 functions to other functions (well adding functions post-facto would
120 probably be a chore... still, doable).
121
122 Deferreds truly shine when code needs to compose asynchronous
123 operations in some way or other, as they can be used as a basis for
124 such composition.
125
126 There are two main forms of compositions over deferred: multiplexing
127 and piping/cascading.
128
129 Deferred multiplexing
130 `````````````````````
131
132 The most common reason for multiplexing deferred is simply performing
133 2+ asynchronous operations and wanting to wait until all of them are
134 done before moving on (and executing more stuff).
135
136 The jQuery multiplexing function for promises is :js:func:`when`.
137
138 .. note:: the multiplexing behavior of jQuery's :js:func:`when` is an
139           (incompatible, mostly) extension of the behavior defined in
140           `CommonJS Promises/B`_.
141
142 This function can take any number of promises [#]_ and will return a
143 promise.
144
145 This returned promise will be resolved when *all* multiplexed promises
146 are resolved, and will be rejected as soon as one of the multiplexed
147 promises is rejected (it behaves like Python's ``all()``, but with
148 promise objects instead of boolean-ish).
149
150 The resolved values of the various promises multiplexed via
151 :js:func:`when` are mapped to the arguments of :js:func:`when`'s
152 success callback, if they are needed. The resolved values of a promise
153 are at the same index in the callback's arguments as the promise in
154 the :js:func:`when` call so you will have:
155
156 .. code-block:: javascript
157
158     $.when(p0, p1, p2, p3).then(
159             function (results0, results1, results2, results3) {
160         // code
161     });
162
163 .. warning::
164
165     in a normal mapping, each parameter to the callback would be an
166     array: each promise is conceptually resolved with an array of 0..n
167     values and these values are passed to :js:func:`when`'s
168     callback. But jQuery treats deferreds resolving a single value
169     specially, and "unwraps" that value.
170
171     For instance, in the code block above if the index of each promise
172     is the number of values it resolves (0 to 3), ``results0`` is an
173     empty array, ``results2`` is an array of 2 elements (a pair) but
174     ``results1`` is the actual value resolved by ``p1``, not an array.
175
176 Deferred chaining
177 `````````````````
178
179 A second useful composition is starting an asynchronous operation as
180 the result of an other asynchronous operation, and wanting the result
181 of both: :js:func:`Deferred.then` returns the deferred on which it was
182 called, so handle e.g. OpenERP's search/read sequence with this would
183 require something along the lines of:
184
185 .. code-block:: javascript
186
187     var result = $.Deferred();
188     Model.search(condition).then(function (ids) {
189         Model.read(ids, fields).then(function (records) {
190             result.resolve(records);
191         });
192     });
193     return result.promise();
194
195 While it doesn't look too bad for trivial code, this quickly gets
196 unwieldy.
197
198 Instead, jQuery provides a tool to handle this kind of chains:
199 :js:func:`Deferred.pipe`.
200
201 :js:func:`~Deferred.pipe` has the same signature as
202 :js:func:`~Deferred.then` and could be used in the same manner
203 provided its return value was not used.
204
205 It differs from :js:func:`~Deferred.then` in two ways: it returns a
206 new promise object, not the one it was called with, and the return
207 values of the callbacks is actually important to it: whichever
208 callback is called,
209
210 * If the callback is not set (not provided or left to null), the
211   resolution or rejection value(s) is simply forwarded to
212   :js:func:`~Deferred.pipe`'s promise (it's essentially a noop)
213
214 * If the callback is set and does not return an observable object (a
215   deferred or a promise), the value it returns (``undefined`` if it
216   does not return anything) will replace the value it was given, e.g.
217
218   .. code-block:: javascript
219
220       promise.pipe(function () {
221           console.log('called');
222       });
223
224   will resolve with the sole value ``undefined``.
225
226 * If the callback is set and returns an observable object, that object
227   will be the actual resolution (and result) of the pipe. This means a
228   resolved promise from the failure callback will resolve the pipe,
229   and a failure promise from the success callback will reject the
230   pipe.
231
232   This provides an easy way to chain operation successes, and the
233   previous piece of code can now be rewritten:
234
235   .. code-block:: javascript
236
237       return Model.search(condition).pipe(function (ids) {
238           return Model.read(ids, fields);
239       });
240
241   the result of the whole expression will encode failure if either
242   ``search`` or ``read`` fails (with the right rejection values), and
243   will be resolved with ``read``'s resolution values if the chain
244   executes correctly.
245
246 :js:func:`~Deferred.pipe` is also useful to adapt third-party
247 promise-based APIs, in order to filter their resolution value counts
248 for instance (to take advantage of :js:func:`when` 's special treatment
249 of single-value promises).
250
251 jQuery.Deferred API
252 ~~~~~~~~~~~~~~~~~~~
253
254 .. js:function:: when(deferreds…)
255
256     :param deferreds: deferred objects to multiplex
257     :returns: a multiplexed deferred
258     :rtype: :js:class:`Deferred`
259
260 .. js:class:: Deferred
261
262     .. js:function:: Deferred.then(doneCallback[, failCallback])
263
264         Attaches new callbacks to the resolution or rejection of the
265         deferred object. Callbacks are executed in the order they are
266         attached to the deferred.
267
268         To provide only a failure callback, pass ``null`` as the
269         ``doneCallback``, to provide only a success callback the
270         second argument can just be ignored (and not passed at all).
271
272         :param doneCallback: function called when the deferred is resolved
273         :type doneCallback: Function
274         :param failCallback: function called when the deferred is rejected
275         :type failCallback: Function
276         :returns: the deferred object on which it was called
277         :rtype: :js:class:`Deferred`
278
279     .. js:function:: Deferred.done(doneCallback)
280
281         Attaches a new success callback to the deferred, shortcut for
282         ``deferred.then(doneCallback)``.
283
284         This is a jQuery extension to `CommonJS Promises/A`_ providing
285         little value over calling :js:func:`~Deferred.then` directly,
286         it should be avoided.
287
288         :param doneCallback: function called when the deferred is resolved
289         :type doneCallback: Function
290         :returns: the deferred object on which it was called
291         :rtype: :js:class:`Deferred`
292
293     .. js:function:: Deferred.fail(failCallback)
294
295         Attaches a new failure callback to the deferred, shortcut for
296         ``deferred.then(null, failCallback)``.
297
298         A second jQuery extension to `Promises/A <CommonJS
299         Promises/A>`_. Although it provides more value than
300         :js:func:`~Deferred.done`, it still is not much and should be
301         avoided as well.
302
303         :param failCallback: function called when the deferred is rejected
304         :type failCallback: Function
305         :returns: the deferred object on which it was called
306         :rtype: :js:class:`Deferred`
307
308     .. js:function:: Deferred.promise()
309
310         Returns a read-only view of the deferred object, with all
311         mutators (resolve and reject) methods removed.
312
313     .. js:function:: Deferred.resolve(value…)
314
315         Called to resolve a deferred, any value provided will be
316         passed onto the success handlers of the deferred object.
317
318         Resolving a deferred which has already been resolved or
319         rejected has no effect.
320
321     .. js:function:: Deferred.reject(value…)
322
323         Called to reject (fail) a deferred, any value provided will be
324         passed onto the failure handler of the deferred object.
325
326         Rejecting a deferred which has already been resolved or
327         rejected has no effect.
328
329     .. js:function:: Deferred.pipe(doneFilter[, failFilter])
330
331         Filters the result of a deferred, able to transform a success
332         into failure and a failure into success, or to delay
333         resolution further.
334
335 .. [#] or simply calling :js:class:`Deferred` as a function, the
336        result is the same
337
338 .. [#] or not-promises, the `CommonJS Promises/B`_ role of
339        :js:func:`when` is to be able to treat values and promises
340        uniformly: :js:func:`when` will pass promises through directly,
341        but non-promise values and objects will be transformed into a
342        resolved promise (resolving themselves with the value itself).
343
344        jQuery's :js:func:`when` keeps this behavior making deferreds
345        easy to build from "static" values, or allowing defensive code
346        where expected promises are wrapped in :js:func:`when` just in
347        case.
348
349 .. _promises: http://en.wikipedia.org/wiki/Promise_(programming)
350 .. _jQuery's deferred: http://api.jquery.com/category/deferred-object/
351 .. _CommonJS Promises/A: http://wiki.commonjs.org/wiki/Promises/A
352 .. _CommonJS Promises/B: http://wiki.commonjs.org/wiki/Promises/B
353 .. _mocks: http://en.wikipedia.org/wiki/Mock_object