promise.js 10 KB

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