aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.7/packages/api-utils/docs/promise.md
blob: d67b82061d19839a5608740ad182f9b2676891f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

## Rationale

Most of the JS APIs are asynchronous complementing it's non-blocking nature.
While this has a good reason and many advantages, it comes with a price.
Instead of structuring our programs into logical black boxes:

    function blackbox(a, b) {
      var c = assemble(a);
      return combine(b, c);
    }


We're forced into continuation passing style, involving lot's of machinery:

    function sphagetti(a, b, callback) {
      assemble(a, function continueWith(error, c) {
        if (error) callback(error);
        else combine(b, c, callback);
      });
    }

This style also makes doing things in sequence hard:

    widget.on('click', function onClick() {
      promptUserForTwitterHandle(function continueWith(error, handle) {
        if (error) return ui.displayError(error);
        twitter.getTweetsFor(handle, funtion continueWith(error, tweets) {
          if (error) return ui.displayError(error);
          ui.showTweets(tweets);
        });
      });
    });

Doing things in parallel is even harder:

    var tweets, answers, checkins;
    twitter.getTweetsFor(user, function continueWith(result) {
      tweets = result;
      somethingFinished();
    });

    stackOverflow.getAnswersFor(question, function continueWith(result) {
      answers = result;
      somethingFinished();
    });

    fourSquare.getCheckinsBy(user, function continueWith(result) {
      checkins=result;
      somethingFinished();
    }); 

    var finished = 0;
    functions somethingFinished() {
      if (++finished === 3)
        ui.show(tweets, answers, checkins);
    }

This also makes error handling quite of an adventure.

## Promises

Consider another approach, where instead of continuation passing via `callback`,
function returns an object, that represents eventual result, either successful
or failed. This object is a promise, both figuratively and by name, to
eventually resolve. We can call a function on the promise to observe
either its fulfillment or rejection. If the promise is rejected and the
rejection is not explicitly observed, any derived promises will be implicitly
rejected for the same reason.

In the Add-on SDK we follow
[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) specification
and model a promise as an object with a `then` method, which can be used to get
the eventual return (fulfillment) value or thrown exception (rejection):

    foo().then(function success(value) {
      // ...
    }, function failure(reason) {
      // ...
    });

If `foo` returns a promise that gets fulfilled with the `value`, `success`
callback (the value handler) will be called with that `value`. However,
if the returned promise gets rejected, the `failure` callback (the error
handler) will be called with the `reason` of an error.

## Propagation

The `then` method of a promise returns a new promise that is resolved with the
return value of either handler. Since function can either return value or throw
an exception, only one handler will be ever called.


    var bar = foo().then(function success(value) {
      // compute something from a value...
    }, function failure(reason) {
      // handle an error...
    });

In this example `bar` is a promise and it's fulfilled by one of two handlers
that are responsible for:

  - If handler returns a value, `bar` will be resolved with it.
  - If handler throws an exception, `bar` will be rejected with it.
  - If handler returns a **promise**, `bar` will "become" that promise. To be
    more precise it will be resolved with a resolution value of the returned
    promise, which will appear and feel as if it was that returned promise.

If the `foo()` promise gets rejected and you omit the error handler, the
**error** will propagate to `bar` (`bar` will be rejected with that error):

    var bar = foo().then(function success(value) {
      // compute something out of the value...
    });

If the `foo()` promise gets fulfilled and you omit the value handler, the
**value** will propagate to `bar` (`bar` will be fulfilled with that value):

    var bar = foo().then(null, function failure(error) {
      // handle error...
    });


## Chaining

There are two ways to chain promise operations. You can chain them using either
inside or outside handlers.

### Flat chaining

You can use `then` for chaining intermediate operations on promises
(`var data = readAsync().then(parse).then(extract)`). You can chain multiple
`then` functions, because `then` returns a promise resolved to a return value
of an operation and errors propagate through the promise chains. In general
good rule of thumb is to prefer `then` based flat chaining. It makes code
easier to read and make changes later:

    var data = readAsync(url).    // read content of url asynchronously
      then(parse).                // parse content from the url
      then(extractQuery).         // extract SQL query
      then(readDBAsync);          // exectue extracted query against DB

### Nested chaining

Flat chaining is not always an option though, as in some cases you may want to
capture an intermediate values of the chain:

    var result = readAsync(url).then(function(source) {
      var json = parse(source)
      return readDBAsync(extractQuery(json)).then(function(data) {
        return writeAsync(json.url, data);
      });
    });

In general, nesting is useful for computing values from more then one promise:

    function eventualAdd(a, b) {
      return a.then(function (a) {
        return b.then(function (b) {
          return a + b;
        });
      });
    }

    var c = eventualAdd(aAsync(), bAsync());

## Error handling

One sometimes-unintuitive aspect of promises is that if you throw an exception
in the value handler, it will not be be caught by the error handler.

    readAsync(url).then(function (value) {
      throw new Error("Can't bar.");
    }, function (error) {
      // We only get here if `readAsync` fails.
    });

To see why this is, consider the parallel between promises and `try`/`catch`.
We are `try`-ing to execute `readAsync()`: the error handler represents a 
`catch` for `readAsync()`, while the value handler represents code that happens
*after* the `try`/`catch` block. That code then needs its own `try`/`catch`
block to handle errors there.

In terms of promises, this means chaining your error handler:

    readAsync(url).
      then(parse).
      then(null, function handleParseError(error) {
        // handle here both `readAsync` and `parse` errors.
    });


# Consuming promises

In general, whole purpose of promises is to avoid a callback spaghetti in the
code. As a matter of fact it would be great if we could convert any synchronous
functions to asynchronous by making it aware of promises. Module exports
`promised` function to do exactly that:

    const { promised } = require('api-utils/promise');
    function sum(x, y) { return x + y }
    var sumAsync = promised(sum);

    var c = sum(a, b);
    var cAsync = asyncSum(aAsync(), bAsinc());

`promised` takes normal function and composes new promise aware version of it
that may take both normal values and promises as arguments and returns promise
that will resolve to value that would have being returned by an original
function if it was called with fulfillment values of given arguments.

This technique is so powerful that it can replace most of the promise utility
functions provided by other promise libraries. For example grouping promises
to observe single resolution of all of them is as simple as this:

    var group = promised(Array);
    var abc = group(aAsync, bAsync, cAsync).then(function(items) {
      return items[0] + items[1] + items[2];
    });

# Making promises

Everything above assumes you get a promise from somewhere else. This
is the common case, but every once in a while, you will need to create a
promise from scratch. Add-on SDK's `promise` module provides API for doing
that.

## defer

Module exports `defer` function, which is where all promises ultimately
come from. Lets see implementation of `readAsync` that we used in lot's
of examples above:

    const { defer } = require('api-utils/promise');
    function readAsync(url) {
      var deferred = defer();

      let xhr = new XMLHttpRequest();
      xhr.open("GET", url, true);
      xhr.onload = function() {
        deferred.resolve(xhr.responseText);
      }
      xhr.onerror = function(event) {
        deferred.reject(event);
      }
      xhr.send();

      return deferred.promise;
    }

So `defer` returns an object that contains `promise` and two `resolve`, `reject`
functions that can be used to resolve / reject that `promise`. **Note:** that
promise can be rejected or resolved and only once. All subsequent attempts will be
ignored.

Another simple example may be `delay` function that returns promise which
is fulfilled with a given `value` in a given `ms`, kind of promise based
alternative to `setTimeout`:

    function delay(ms, value) {
      let { promise, resolve } = defer();
      setTimeout(resolve, ms, value);
      return promise;
    }

    delay(10, 'Hello world').then(console.log);
    // After 10ms => 'Helo world'

# Advanced usage

If general `defer` and `promised` should be enough to doing almost anything
you may think of with promises, but once you start using promises extensively
you may discover some missing pieces and this section of documentation may
help you to discover them.

## Doing things concurrently

So far we have being playing with promises that do things sequentially, but
there are bunch of cases where one would need to do things concurrently. In the
following example we implement functions that takes multiple promises and
returns one that resolves to first on being fulfilled:

    function race() {
      let { promise, resolve } = defer();
      Array.slice(arguments).forEach(function(promise) {
        promise.then(resolve);
      });
      return promise;
    }

    var asyncAorB = race(readAsync(urlA), readAsync(urlB));

*Note: that this implementation forgives failures and would fail if all
promises fail to resolve.*

There are cases when promise may or may not be fulfilled in a reasonable time.
In such cases it's useful to put a timer on such tasks:

    function timeout(promise, ms) {
      let deferred = defer();
      promise.then(deferred.resolve, deferred.reject);
      delay(ms, 'timeout').then(deferred.reject);
      return deferred.promise;
    }

    var tweets = readAsync(url);
    timeout(tweets, 20).then(function(data) {
      ui.display(data);
    }, function() {
      alert('Network is being too slow, try again later');
    });

## Alternative promise APIs

There may be a cases where you will want to provide more than just `then`
method on your promises. In fact some other promise frameworks do that.
Such use cases are also supported. Earlier described `defer` may be passed
optional `prototype` argument, in order to make returned promise and all
the subsequent promises decedents of that `prototype`:

    let { promise, resolve } = defer({
      get: function get(name) {
        return this.then(function(value) {
          return value[name];
        })
      }
    });

    promise.get('foo').get('bar').then(console.log);
    resolve({ foo: { bar: 'taram !!' } });

    // => 'taram !!'

Also `promised` function maybe passed second optional `prototype` argument to
achieve same effect.

## Treat all values as promises

Module provides a simple function for wrapping values into promises:

    const { resolve } = require('api-utils/promise');

    var a = resolve(5).then(function(value) {
      return value + 2
    });
    a.then(console.log);  // => 7

Also `resolve` not only takes values, but also promises. If you pass it
a promise it will return new identical one:

    const { resolve } = require('api-utils/promise');

    resolve(resolve(resolve(3))).then(console.log); // => 3

If this construct may look strange at first, but it becomes quite handy
when writing functions that deal with both promises and values. In such
cases it's usually easier to wrap value into promise than branch on value
type:

    function or(a, b) {
      var second = resolve(b).then(function(bValue) { return !!bValue });
      return resolve(a).then(function(aValue) {
        return !!aValue || second;
      }, function() {
        return second;
      })
    }

*Note: We could not use `promised` function here, as they reject returned
promise if any of the given arguments is rejected.*

If you need to customize your promises even further you may pass `resolve` a
second optional `prototype` argument that will have same effect as with `defer`.

## Treat errors as promises

Now that we can create all kinds of eventual values, it's useful to have a
way to create eventual errors. Module exports `reject` exactly for that.
It takes anything as an argument and returns a promise that is rejected with
it.

    const { reject } = require('api-utils/promise');

    var boom = reject(Error('boom!'));

    future(function() {
      return Math.random() < 0.5 ? boom : value
    })

As with rest of the APIs error may be given second optional `prototype`
argument to customize resulting promise to your needs.