1 | import {
|
2 | isFunction
|
3 | } from './utils';
|
4 | import {
|
5 | noop,
|
6 | nextId,
|
7 | PROMISE_ID,
|
8 | initializePromise
|
9 | } from './-internal';
|
10 | import {
|
11 | asap,
|
12 | setAsap,
|
13 | setScheduler
|
14 | } from './asap';
|
15 |
|
16 | import all from './promise/all';
|
17 | import race from './promise/race';
|
18 | import Resolve from './promise/resolve';
|
19 | import Reject from './promise/reject';
|
20 | import then from './then';
|
21 |
|
22 | function needsResolver() {
|
23 | throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
|
24 | }
|
25 |
|
26 | function needsNew() {
|
27 | throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
|
28 | }
|
29 |
|
30 | /**
|
31 | Promise objects represent the eventual result of an asynchronous operation. The
|
32 | primary way of interacting with a promise is through its `then` method, which
|
33 | registers callbacks to receive either a promise's eventual value or the reason
|
34 | why the promise cannot be fulfilled.
|
35 |
|
36 | Terminology
|
37 | -----------
|
38 |
|
39 | - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
|
40 | - `thenable` is an object or function that defines a `then` method.
|
41 | - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
|
42 | - `exception` is a value that is thrown using the throw statement.
|
43 | - `reason` is a value that indicates why a promise was rejected.
|
44 | - `settled` the final resting state of a promise, fulfilled or rejected.
|
45 |
|
46 | A promise can be in one of three states: pending, fulfilled, or rejected.
|
47 |
|
48 | Promises that are fulfilled have a fulfillment value and are in the fulfilled
|
49 | state. Promises that are rejected have a rejection reason and are in the
|
50 | rejected state. A fulfillment value is never a thenable.
|
51 |
|
52 | Promises can also be said to *resolve* a value. If this value is also a
|
53 | promise, then the original promise's settled state will match the value's
|
54 | settled state. So a promise that *resolves* a promise that rejects will
|
55 | itself reject, and a promise that *resolves* a promise that fulfills will
|
56 | itself fulfill.
|
57 |
|
58 |
|
59 | Basic Usage:
|
60 | ------------
|
61 |
|
62 | ```js
|
63 | let promise = new Promise(function(resolve, reject) {
|
64 | // on success
|
65 | resolve(value);
|
66 |
|
67 | // on failure
|
68 | reject(reason);
|
69 | });
|
70 |
|
71 | promise.then(function(value) {
|
72 | // on fulfillment
|
73 | }, function(reason) {
|
74 | // on rejection
|
75 | });
|
76 | ```
|
77 |
|
78 | Advanced Usage:
|
79 | ---------------
|
80 |
|
81 | Promises shine when abstracting away asynchronous interactions such as
|
82 | `XMLHttpRequest`s.
|
83 |
|
84 | ```js
|
85 | function getJSON(url) {
|
86 | return new Promise(function(resolve, reject){
|
87 | let xhr = new XMLHttpRequest();
|
88 |
|
89 | xhr.open('GET', url);
|
90 | xhr.onreadystatechange = handler;
|
91 | xhr.responseType = 'json';
|
92 | xhr.setRequestHeader('Accept', 'application/json');
|
93 | xhr.send();
|
94 |
|
95 | function handler() {
|
96 | if (this.readyState === this.DONE) {
|
97 | if (this.status === 200) {
|
98 | resolve(this.response);
|
99 | } else {
|
100 | reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
|
101 | }
|
102 | }
|
103 | };
|
104 | });
|
105 | }
|
106 |
|
107 | getJSON('/posts.json').then(function(json) {
|
108 | // on fulfillment
|
109 | }, function(reason) {
|
110 | // on rejection
|
111 | });
|
112 | ```
|
113 |
|
114 | Unlike callbacks, promises are great composable primitives.
|
115 |
|
116 | ```js
|
117 | Promise.all([
|
118 | getJSON('/posts'),
|
119 | getJSON('/comments')
|
120 | ]).then(function(values){
|
121 | values[0] // => postsJSON
|
122 | values[1] // => commentsJSON
|
123 |
|
124 | return values;
|
125 | });
|
126 | ```
|
127 |
|
128 | @class Promise
|
129 | @param {Function} resolver
|
130 | Useful for tooling.
|
131 | @constructor
|
132 | */
|
133 |
|
134 | class Promise {
|
135 | constructor(resolver) {
|
136 | this[PROMISE_ID] = nextId();
|
137 | this._result = this._state = undefined;
|
138 | this._subscribers = [];
|
139 |
|
140 | if (noop !== resolver) {
|
141 | typeof resolver !== 'function' && needsResolver();
|
142 | this instanceof Promise ? initializePromise(this, resolver) : needsNew();
|
143 | }
|
144 | }
|
145 |
|
146 | /**
|
147 | The primary way of interacting with a promise is through its `then` method,
|
148 | which registers callbacks to receive either a promise's eventual value or the
|
149 | reason why the promise cannot be fulfilled.
|
150 |
|
151 | ```js
|
152 | findUser().then(function(user){
|
153 | // user is available
|
154 | }, function(reason){
|
155 | // user is unavailable, and you are given the reason why
|
156 | });
|
157 | ```
|
158 |
|
159 | Chaining
|
160 | --------
|
161 |
|
162 | The return value of `then` is itself a promise. This second, 'downstream'
|
163 | promise is resolved with the return value of the first promise's fulfillment
|
164 | or rejection handler, or rejected if the handler throws an exception.
|
165 |
|
166 | ```js
|
167 | findUser().then(function (user) {
|
168 | return user.name;
|
169 | }, function (reason) {
|
170 | return 'default name';
|
171 | }).then(function (userName) {
|
172 | // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
|
173 | // will be `'default name'`
|
174 | });
|
175 |
|
176 | findUser().then(function (user) {
|
177 | throw new Error('Found user, but still unhappy');
|
178 | }, function (reason) {
|
179 | throw new Error('`findUser` rejected and we're unhappy');
|
180 | }).then(function (value) {
|
181 | // never reached
|
182 | }, function (reason) {
|
183 | // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
|
184 | // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
|
185 | });
|
186 | ```
|
187 | If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
|
188 |
|
189 | ```js
|
190 | findUser().then(function (user) {
|
191 | throw new PedagogicalException('Upstream error');
|
192 | }).then(function (value) {
|
193 | // never reached
|
194 | }).then(function (value) {
|
195 | // never reached
|
196 | }, function (reason) {
|
197 | // The `PedgagocialException` is propagated all the way down to here
|
198 | });
|
199 | ```
|
200 |
|
201 | Assimilation
|
202 | ------------
|
203 |
|
204 | Sometimes the value you want to propagate to a downstream promise can only be
|
205 | retrieved asynchronously. This can be achieved by returning a promise in the
|
206 | fulfillment or rejection handler. The downstream promise will then be pending
|
207 | until the returned promise is settled. This is called *assimilation*.
|
208 |
|
209 | ```js
|
210 | findUser().then(function (user) {
|
211 | return findCommentsByAuthor(user);
|
212 | }).then(function (comments) {
|
213 | // The user's comments are now available
|
214 | });
|
215 | ```
|
216 |
|
217 | If the assimliated promise rejects, then the downstream promise will also reject.
|
218 |
|
219 | ```js
|
220 | findUser().then(function (user) {
|
221 | return findCommentsByAuthor(user);
|
222 | }).then(function (comments) {
|
223 | // If `findCommentsByAuthor` fulfills, we'll have the value here
|
224 | }, function (reason) {
|
225 | // If `findCommentsByAuthor` rejects, we'll have the reason here
|
226 | });
|
227 | ```
|
228 |
|
229 | Simple Example
|
230 | --------------
|
231 |
|
232 | Synchronous Example
|
233 |
|
234 | ```javascript
|
235 | let result;
|
236 |
|
237 | try {
|
238 | result = findResult();
|
239 | // success
|
240 | } catch(reason) {
|
241 | // failure
|
242 | }
|
243 | ```
|
244 |
|
245 | Errback Example
|
246 |
|
247 | ```js
|
248 | findResult(function(result, err){
|
249 | if (err) {
|
250 | // failure
|
251 | } else {
|
252 | // success
|
253 | }
|
254 | });
|
255 | ```
|
256 |
|
257 | Promise Example;
|
258 |
|
259 | ```javascript
|
260 | findResult().then(function(result){
|
261 | // success
|
262 | }, function(reason){
|
263 | // failure
|
264 | });
|
265 | ```
|
266 |
|
267 | Advanced Example
|
268 | --------------
|
269 |
|
270 | Synchronous Example
|
271 |
|
272 | ```javascript
|
273 | let author, books;
|
274 |
|
275 | try {
|
276 | author = findAuthor();
|
277 | books = findBooksByAuthor(author);
|
278 | // success
|
279 | } catch(reason) {
|
280 | // failure
|
281 | }
|
282 | ```
|
283 |
|
284 | Errback Example
|
285 |
|
286 | ```js
|
287 |
|
288 | function foundBooks(books) {
|
289 |
|
290 | }
|
291 |
|
292 | function failure(reason) {
|
293 |
|
294 | }
|
295 |
|
296 | findAuthor(function(author, err){
|
297 | if (err) {
|
298 | failure(err);
|
299 | // failure
|
300 | } else {
|
301 | try {
|
302 | findBoooksByAuthor(author, function(books, err) {
|
303 | if (err) {
|
304 | failure(err);
|
305 | } else {
|
306 | try {
|
307 | foundBooks(books);
|
308 | } catch(reason) {
|
309 | failure(reason);
|
310 | }
|
311 | }
|
312 | });
|
313 | } catch(error) {
|
314 | failure(err);
|
315 | }
|
316 | // success
|
317 | }
|
318 | });
|
319 | ```
|
320 |
|
321 | Promise Example;
|
322 |
|
323 | ```javascript
|
324 | findAuthor().
|
325 | then(findBooksByAuthor).
|
326 | then(function(books){
|
327 | // found books
|
328 | }).catch(function(reason){
|
329 | // something went wrong
|
330 | });
|
331 | ```
|
332 |
|
333 | @method then
|
334 | @param {Function} onFulfilled
|
335 | @param {Function} onRejected
|
336 | Useful for tooling.
|
337 | @return {Promise}
|
338 | */
|
339 |
|
340 | /**
|
341 | `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
|
342 | as the catch block of a try/catch statement.
|
343 |
|
344 | ```js
|
345 | function findAuthor(){
|
346 | throw new Error('couldn't find that author');
|
347 | }
|
348 |
|
349 | // synchronous
|
350 | try {
|
351 | findAuthor();
|
352 | } catch(reason) {
|
353 | // something went wrong
|
354 | }
|
355 |
|
356 | // async with promises
|
357 | findAuthor().catch(function(reason){
|
358 | // something went wrong
|
359 | });
|
360 | ```
|
361 |
|
362 | @method catch
|
363 | @param {Function} onRejection
|
364 | Useful for tooling.
|
365 | @return {Promise}
|
366 | */
|
367 | catch(onRejection) {
|
368 | return this.then(null, onRejection);
|
369 | }
|
370 |
|
371 | /**
|
372 | `finally` will be invoked regardless of the promise's fate just as native
|
373 | try/catch/finally behaves
|
374 |
|
375 | Synchronous example:
|
376 |
|
377 | ```js
|
378 | findAuthor() {
|
379 | if (Math.random() > 0.5) {
|
380 | throw new Error();
|
381 | }
|
382 | return new Author();
|
383 | }
|
384 |
|
385 | try {
|
386 | return findAuthor(); // succeed or fail
|
387 | } catch(error) {
|
388 | return findOtherAuther();
|
389 | } finally {
|
390 | // always runs
|
391 | // doesn't affect the return value
|
392 | }
|
393 | ```
|
394 |
|
395 | Asynchronous example:
|
396 |
|
397 | ```js
|
398 | findAuthor().catch(function(reason){
|
399 | return findOtherAuther();
|
400 | }).finally(function(){
|
401 | // author was either found, or not
|
402 | });
|
403 | ```
|
404 |
|
405 | @method finally
|
406 | @param {Function} callback
|
407 | @return {Promise}
|
408 | */
|
409 | finally(callback) {
|
410 | let promise = this;
|
411 | let constructor = promise.constructor;
|
412 |
|
413 | if ( isFunction(callback) ) {
|
414 | return promise.then(value => constructor.resolve(callback()).then(() => value),
|
415 | reason => constructor.resolve(callback()).then(() => { throw reason; }));
|
416 | }
|
417 |
|
418 | return promise.then(callback, callback);
|
419 | }
|
420 | }
|
421 |
|
422 | Promise.prototype.then = then;
|
423 | export default Promise;
|
424 | Promise.all = all;
|
425 | Promise.race = race;
|
426 | Promise.resolve = Resolve;
|
427 | Promise.reject = Reject;
|
428 | Promise._setScheduler = setScheduler;
|
429 | Promise._setAsap = setAsap;
|
430 | Promise._asap = asap;
|
431 |
|