aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.7/packages/api-utils/docs/promise.md
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.7/packages/api-utils/docs/promise.md')
-rw-r--r--tools/addon-sdk-1.7/packages/api-utils/docs/promise.md394
1 files changed, 394 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.7/packages/api-utils/docs/promise.md b/tools/addon-sdk-1.7/packages/api-utils/docs/promise.md
new file mode 100644
index 0000000..d67b820
--- /dev/null
+++ b/tools/addon-sdk-1.7/packages/api-utils/docs/promise.md
@@ -0,0 +1,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.