promise.js 9.8 KB

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