diff options
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.md | 394 |
1 files changed, 0 insertions, 394 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 deleted file mode 100644 index d67b820..0000000 --- a/tools/addon-sdk-1.7/packages/api-utils/docs/promise.md +++ /dev/null @@ -1,394 +0,0 @@ -<!-- 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. |