432 lines
9.8 KiB
JavaScript
432 lines
9.8 KiB
JavaScript
import {
|
|
isFunction
|
|
} from './utils';
|
|
import {
|
|
noop,
|
|
nextId,
|
|
PROMISE_ID,
|
|
initializePromise
|
|
} from './-internal';
|
|
import {
|
|
asap,
|
|
setAsap,
|
|
setScheduler
|
|
} from './asap';
|
|
|
|
import all from './promise/all';
|
|
import race from './promise/race';
|
|
import Resolve from './promise/resolve';
|
|
import Reject from './promise/reject';
|
|
import then from './then';
|
|
|
|
function needsResolver() {
|
|
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
|
|
}
|
|
|
|
function needsNew() {
|
|
throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
|
|
}
|
|
|
|
/**
|
|
Promise objects represent the eventual result of an asynchronous operation. The
|
|
primary way of interacting with a promise is through its `then` method, which
|
|
registers callbacks to receive either a promise's eventual value or the reason
|
|
why the promise cannot be fulfilled.
|
|
|
|
Terminology
|
|
-----------
|
|
|
|
- `promise` is an object or function with a `then` method whose behavior conforms to this specification.
|
|
- `thenable` is an object or function that defines a `then` method.
|
|
- `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
|
|
- `exception` is a value that is thrown using the throw statement.
|
|
- `reason` is a value that indicates why a promise was rejected.
|
|
- `settled` the final resting state of a promise, fulfilled or rejected.
|
|
|
|
A promise can be in one of three states: pending, fulfilled, or rejected.
|
|
|
|
Promises that are fulfilled have a fulfillment value and are in the fulfilled
|
|
state. Promises that are rejected have a rejection reason and are in the
|
|
rejected state. A fulfillment value is never a thenable.
|
|
|
|
Promises can also be said to *resolve* a value. If this value is also a
|
|
promise, then the original promise's settled state will match the value's
|
|
settled state. So a promise that *resolves* a promise that rejects will
|
|
itself reject, and a promise that *resolves* a promise that fulfills will
|
|
itself fulfill.
|
|
|
|
|
|
Basic Usage:
|
|
------------
|
|
|
|
```js
|
|
let promise = new Promise(function(resolve, reject) {
|
|
// on success
|
|
resolve(value);
|
|
|
|
// on failure
|
|
reject(reason);
|
|
});
|
|
|
|
promise.then(function(value) {
|
|
// on fulfillment
|
|
}, function(reason) {
|
|
// on rejection
|
|
});
|
|
```
|
|
|
|
Advanced Usage:
|
|
---------------
|
|
|
|
Promises shine when abstracting away asynchronous interactions such as
|
|
`XMLHttpRequest`s.
|
|
|
|
```js
|
|
function getJSON(url) {
|
|
return new Promise(function(resolve, reject){
|
|
let xhr = new XMLHttpRequest();
|
|
|
|
xhr.open('GET', url);
|
|
xhr.onreadystatechange = handler;
|
|
xhr.responseType = 'json';
|
|
xhr.setRequestHeader('Accept', 'application/json');
|
|
xhr.send();
|
|
|
|
function handler() {
|
|
if (this.readyState === this.DONE) {
|
|
if (this.status === 200) {
|
|
resolve(this.response);
|
|
} else {
|
|
reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
|
|
}
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
getJSON('/posts.json').then(function(json) {
|
|
// on fulfillment
|
|
}, function(reason) {
|
|
// on rejection
|
|
});
|
|
```
|
|
|
|
Unlike callbacks, promises are great composable primitives.
|
|
|
|
```js
|
|
Promise.all([
|
|
getJSON('/posts'),
|
|
getJSON('/comments')
|
|
]).then(function(values){
|
|
values[0] // => postsJSON
|
|
values[1] // => commentsJSON
|
|
|
|
return values;
|
|
});
|
|
```
|
|
|
|
@class Promise
|
|
@param {Function} resolver
|
|
Useful for tooling.
|
|
@constructor
|
|
*/
|
|
|
|
class Promise {
|
|
constructor(resolver) {
|
|
this[PROMISE_ID] = nextId();
|
|
this._result = this._state = undefined;
|
|
this._subscribers = [];
|
|
|
|
if (noop !== resolver) {
|
|
typeof resolver !== 'function' && needsResolver();
|
|
this instanceof Promise ? initializePromise(this, resolver) : needsNew();
|
|
}
|
|
}
|
|
|
|
/**
|
|
The primary way of interacting with a promise is through its `then` method,
|
|
which registers callbacks to receive either a promise's eventual value or the
|
|
reason why the promise cannot be fulfilled.
|
|
|
|
```js
|
|
findUser().then(function(user){
|
|
// user is available
|
|
}, function(reason){
|
|
// user is unavailable, and you are given the reason why
|
|
});
|
|
```
|
|
|
|
Chaining
|
|
--------
|
|
|
|
The return value of `then` is itself a promise. This second, 'downstream'
|
|
promise is resolved with the return value of the first promise's fulfillment
|
|
or rejection handler, or rejected if the handler throws an exception.
|
|
|
|
```js
|
|
findUser().then(function (user) {
|
|
return user.name;
|
|
}, function (reason) {
|
|
return 'default name';
|
|
}).then(function (userName) {
|
|
// If `findUser` fulfilled, `userName` will be the user's name, otherwise it
|
|
// will be `'default name'`
|
|
});
|
|
|
|
findUser().then(function (user) {
|
|
throw new Error('Found user, but still unhappy');
|
|
}, function (reason) {
|
|
throw new Error('`findUser` rejected and we're unhappy');
|
|
}).then(function (value) {
|
|
// never reached
|
|
}, function (reason) {
|
|
// if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
|
|
// If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
|
|
});
|
|
```
|
|
If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
|
|
|
|
```js
|
|
findUser().then(function (user) {
|
|
throw new PedagogicalException('Upstream error');
|
|
}).then(function (value) {
|
|
// never reached
|
|
}).then(function (value) {
|
|
// never reached
|
|
}, function (reason) {
|
|
// The `PedgagocialException` is propagated all the way down to here
|
|
});
|
|
```
|
|
|
|
Assimilation
|
|
------------
|
|
|
|
Sometimes the value you want to propagate to a downstream promise can only be
|
|
retrieved asynchronously. This can be achieved by returning a promise in the
|
|
fulfillment or rejection handler. The downstream promise will then be pending
|
|
until the returned promise is settled. This is called *assimilation*.
|
|
|
|
```js
|
|
findUser().then(function (user) {
|
|
return findCommentsByAuthor(user);
|
|
}).then(function (comments) {
|
|
// The user's comments are now available
|
|
});
|
|
```
|
|
|
|
If the assimliated promise rejects, then the downstream promise will also reject.
|
|
|
|
```js
|
|
findUser().then(function (user) {
|
|
return findCommentsByAuthor(user);
|
|
}).then(function (comments) {
|
|
// If `findCommentsByAuthor` fulfills, we'll have the value here
|
|
}, function (reason) {
|
|
// If `findCommentsByAuthor` rejects, we'll have the reason here
|
|
});
|
|
```
|
|
|
|
Simple Example
|
|
--------------
|
|
|
|
Synchronous Example
|
|
|
|
```javascript
|
|
let result;
|
|
|
|
try {
|
|
result = findResult();
|
|
// success
|
|
} catch(reason) {
|
|
// failure
|
|
}
|
|
```
|
|
|
|
Errback Example
|
|
|
|
```js
|
|
findResult(function(result, err){
|
|
if (err) {
|
|
// failure
|
|
} else {
|
|
// success
|
|
}
|
|
});
|
|
```
|
|
|
|
Promise Example;
|
|
|
|
```javascript
|
|
findResult().then(function(result){
|
|
// success
|
|
}, function(reason){
|
|
// failure
|
|
});
|
|
```
|
|
|
|
Advanced Example
|
|
--------------
|
|
|
|
Synchronous Example
|
|
|
|
```javascript
|
|
let author, books;
|
|
|
|
try {
|
|
author = findAuthor();
|
|
books = findBooksByAuthor(author);
|
|
// success
|
|
} catch(reason) {
|
|
// failure
|
|
}
|
|
```
|
|
|
|
Errback Example
|
|
|
|
```js
|
|
|
|
function foundBooks(books) {
|
|
|
|
}
|
|
|
|
function failure(reason) {
|
|
|
|
}
|
|
|
|
findAuthor(function(author, err){
|
|
if (err) {
|
|
failure(err);
|
|
// failure
|
|
} else {
|
|
try {
|
|
findBoooksByAuthor(author, function(books, err) {
|
|
if (err) {
|
|
failure(err);
|
|
} else {
|
|
try {
|
|
foundBooks(books);
|
|
} catch(reason) {
|
|
failure(reason);
|
|
}
|
|
}
|
|
});
|
|
} catch(error) {
|
|
failure(err);
|
|
}
|
|
// success
|
|
}
|
|
});
|
|
```
|
|
|
|
Promise Example;
|
|
|
|
```javascript
|
|
findAuthor().
|
|
then(findBooksByAuthor).
|
|
then(function(books){
|
|
// found books
|
|
}).catch(function(reason){
|
|
// something went wrong
|
|
});
|
|
```
|
|
|
|
@method then
|
|
@param {Function} onFulfilled
|
|
@param {Function} onRejected
|
|
Useful for tooling.
|
|
@return {Promise}
|
|
*/
|
|
|
|
/**
|
|
`catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
|
|
as the catch block of a try/catch statement.
|
|
|
|
```js
|
|
function findAuthor(){
|
|
throw new Error('couldn't find that author');
|
|
}
|
|
|
|
// synchronous
|
|
try {
|
|
findAuthor();
|
|
} catch(reason) {
|
|
// something went wrong
|
|
}
|
|
|
|
// async with promises
|
|
findAuthor().catch(function(reason){
|
|
// something went wrong
|
|
});
|
|
```
|
|
|
|
@method catch
|
|
@param {Function} onRejection
|
|
Useful for tooling.
|
|
@return {Promise}
|
|
*/
|
|
catch(onRejection) {
|
|
return this.then(null, onRejection);
|
|
}
|
|
|
|
/**
|
|
`finally` will be invoked regardless of the promise's fate just as native
|
|
try/catch/finally behaves
|
|
|
|
Synchronous example:
|
|
|
|
```js
|
|
findAuthor() {
|
|
if (Math.random() > 0.5) {
|
|
throw new Error();
|
|
}
|
|
return new Author();
|
|
}
|
|
|
|
try {
|
|
return findAuthor(); // succeed or fail
|
|
} catch(error) {
|
|
return findOtherAuther();
|
|
} finally {
|
|
// always runs
|
|
// doesn't affect the return value
|
|
}
|
|
```
|
|
|
|
Asynchronous example:
|
|
|
|
```js
|
|
findAuthor().catch(function(reason){
|
|
return findOtherAuther();
|
|
}).finally(function(){
|
|
// author was either found, or not
|
|
});
|
|
```
|
|
|
|
@method finally
|
|
@param {Function} callback
|
|
@return {Promise}
|
|
*/
|
|
finally(callback) {
|
|
let promise = this;
|
|
let constructor = promise.constructor;
|
|
|
|
if ( isFunction(callback) ) {
|
|
return promise.then(value => constructor.resolve(callback()).then(() => value),
|
|
reason => constructor.resolve(callback()).then(() => { throw reason; }));
|
|
}
|
|
|
|
return promise.then(callback, callback);
|
|
}
|
|
}
|
|
|
|
Promise.prototype.then = then;
|
|
export default Promise;
|
|
Promise.all = all;
|
|
Promise.race = race;
|
|
Promise.resolve = Resolve;
|
|
Promise.reject = Reject;
|
|
Promise._setScheduler = setScheduler;
|
|
Promise._setAsap = setAsap;
|
|
Promise._asap = asap;
|
|
|