API Docs for: 3.18.1
Show:

File: promise/js/promise.js

  1. /**
  2. Wraps the execution of asynchronous operations, providing a promise object that
  3. can be used to subscribe to the various ways the operation may terminate.
  4.  
  5. When the operation completes successfully, call the Resolver's `resolve()`
  6. method, passing any relevant response data for subscribers. If the operation
  7. encounters an error or is unsuccessful in some way, call `reject()`, again
  8. passing any relevant data for subscribers.
  9.  
  10. The Resolver object should be shared only with the code resposible for
  11. resolving or rejecting it. Public access for the Resolver is through its
  12. _promise_, which is returned from the Resolver's `promise` property. While both
  13. Resolver and promise allow subscriptions to the Resolver's state changes, the
  14. promise may be exposed to non-controlling code. It is the preferable interface
  15. for adding subscriptions.
  16.  
  17. Subscribe to state changes in the Resolver with the promise's
  18. `then(callback, errback)` method. `then()` wraps the passed callbacks in a
  19. new Resolver and returns the corresponding promise, allowing chaining of
  20. asynchronous or synchronous operations. E.g.
  21. `promise.then(someAsyncFunc).then(anotherAsyncFunc)`
  22.  
  23. @module promise
  24. @since 3.9.0
  25. **/
  26.  
  27. var Lang = Y.Lang,
  28. slice = [].slice;
  29.  
  30. /**
  31. A promise represents a value that may not yet be available. Promises allow
  32. you to chain asynchronous operations, write synchronous looking code and
  33. handle errors throughout the process.
  34.  
  35. This constructor takes a function as a parameter where you can insert the logic
  36. that fulfills or rejects this promise. The fulfillment value and the rejection
  37. reason can be any JavaScript value. It's encouraged that rejection reasons be
  38. error objects
  39.  
  40. <pre><code>
  41. var fulfilled = new Y.Promise(function (resolve) {
  42. resolve('I am a fulfilled promise');
  43. });
  44.  
  45. var rejected = new Y.Promise(function (resolve, reject) {
  46. reject(new Error('I am a rejected promise'));
  47. });
  48. </code></pre>
  49.  
  50. @class Promise
  51. @constructor
  52. @param {Function} fn A function where to insert the logic that resolves this
  53. promise. Receives `resolve` and `reject` functions as parameters.
  54. This function is called synchronously.
  55. **/
  56. function Promise(fn) {
  57. if (!(this instanceof Promise)) {
  58. Y.log('Promises should always be created with new Promise(). This will throw an error in the future', 'warn', NAME);
  59. return new Promise(fn);
  60. }
  61.  
  62. var resolver = new Promise.Resolver(this);
  63.  
  64. /**
  65. A reference to the resolver object that handles this promise
  66.  
  67. @property _resolver
  68. @type Object
  69. @private
  70. */
  71. this._resolver = resolver;
  72.  
  73. try {
  74. fn.call(this, function (value) {
  75. resolver.resolve(value);
  76. }, function (reason) {
  77. resolver.reject(reason);
  78. });
  79. } catch (e) {
  80. resolver.reject(e);
  81. }
  82. }
  83.  
  84. Y.mix(Promise.prototype, {
  85. /**
  86. Schedule execution of a callback to either or both of "fulfill" and
  87. "reject" resolutions for this promise. The callbacks are wrapped in a new
  88. promise and that promise is returned. This allows operation chaining ala
  89. `functionA().then(functionB).then(functionC)` where `functionA` returns
  90. a promise, and `functionB` and `functionC` _may_ return promises.
  91.  
  92. Asynchronicity of the callbacks is guaranteed.
  93.  
  94. @method then
  95. @param {Function} [callback] function to execute if the promise
  96. resolves successfully
  97. @param {Function} [errback] function to execute if the promise
  98. resolves unsuccessfully
  99. @return {Promise} A promise wrapping the resolution of either "resolve" or
  100. "reject" callback
  101. **/
  102. then: function (callback, errback) {
  103. var Constructor = this.constructor,
  104. resolver = this._resolver;
  105.  
  106. // using this.constructor allows for customized promises to be
  107. // returned instead of plain ones
  108. return new Constructor(function (resolve, reject) {
  109. resolver._addCallbacks(
  110. // Check if callbacks are functions. If not, default to
  111. // `resolve` and `reject` respectively.
  112. // The wrapping of the callbacks is done here and not in
  113. // `_addCallbacks` because it is a feature specific to `then`.
  114. // If `done` is added to promises it would call `_addCallbacks`
  115. // without defaulting to anything and without wrapping
  116. typeof callback === 'function' ?
  117. Promise._wrap(resolve, reject, callback) : resolve,
  118. typeof errback === 'function' ?
  119. Promise._wrap(resolve, reject, errback) : reject
  120. );
  121. });
  122. },
  123.  
  124. /**
  125. A shorthand for `promise.then(undefined, callback)`.
  126.  
  127. Returns a new promise and the error callback gets the same treatment as in
  128. `then`: errors get caught and turned into rejections, and the return value
  129. of the callback becomes the fulfilled value of the returned promise.
  130.  
  131. @method catch
  132. @param [Function] errback Callback to be called in case this promise is
  133. rejected
  134. @return {Promise} A new promise modified by the behavior of the error
  135. callback
  136. **/
  137. 'catch': function (errback) {
  138. return this.then(undefined, errback);
  139. },
  140.  
  141. /**
  142. Returns the current status of the operation. Possible results are
  143. "pending", "fulfilled", and "rejected".
  144.  
  145. @method getStatus
  146. @return {String}
  147. @deprecated
  148. **/
  149. getStatus: function () {
  150. Y.log('promise.getStatus() will be removed in the future', 'warn', NAME);
  151. return this._resolver.getStatus();
  152. }
  153. });
  154.  
  155. /**
  156. Wraps the callback in another function to catch exceptions and turn them into
  157. rejections.
  158.  
  159. @method _wrap
  160. @param {Function} resolve Resolving function of the resolver that
  161. handles this promise
  162. @param {Function} reject Rejection function of the resolver that
  163. handles this promise
  164. @param {Function} fn Callback to wrap
  165. @return {Function}
  166. @private
  167. **/
  168. Promise._wrap = function (resolve, reject, fn) {
  169. // callbacks and errbacks only get one argument
  170. return function (valueOrReason) {
  171. var result;
  172.  
  173. // Promises model exception handling through callbacks
  174. // making both synchronous and asynchronous errors behave
  175. // the same way
  176. try {
  177. // Use the argument coming in to the callback/errback from the
  178. // resolution of the parent promise.
  179. // The function must be called as a normal function, with no
  180. // special value for |this|, as per Promises A+
  181. result = fn(valueOrReason);
  182. } catch (e) {
  183. reject(e);
  184. return;
  185. }
  186.  
  187. resolve(result);
  188. };
  189. };
  190.  
  191. /**
  192. Checks if an object or value is a promise. This is cross-implementation
  193. compatible, so promises returned from other libraries or native components
  194. that are compatible with the Promises A+ spec should be recognized by this
  195. method.
  196.  
  197. @method isPromise
  198. @param {Any} obj The object to test
  199. @return {Boolean} Whether the object is a promise or not
  200. @static
  201. **/
  202. Promise.isPromise = function (obj) {
  203. var then;
  204. // We test promises by structure to be able to identify other
  205. // implementations' promises. This is important for cross compatibility and
  206. // In particular Y.when which should recognize any kind of promise
  207. // Use try...catch when retrieving obj.then. Return false if it throws
  208. // See Promises/A+ 1.1
  209. try {
  210. then = obj.then;
  211. } catch (_) {}
  212. return typeof then === 'function';
  213. };
  214.  
  215. /**
  216. Ensures that a certain value is a promise. If it is not a promise, it wraps it
  217. in one.
  218.  
  219. This method can be copied or inherited in subclasses. In that case it will
  220. check that the value passed to it is an instance of the correct class.
  221. This means that `PromiseSubclass.resolve()` will always return instances of
  222. `PromiseSubclass`.
  223.  
  224. @method resolve
  225. @param {Any} Any object that may or may not be a promise
  226. @return {Promise}
  227. @static
  228. **/
  229. Promise.resolve = function (value) {
  230. return Promise.isPromise(value) && value.constructor === this ? value :
  231. /*jshint newcap: false */
  232. new this(function (resolve) {
  233. /*jshint newcap: true */
  234. resolve(value);
  235. });
  236. };
  237.  
  238. /**
  239. A shorthand for creating a rejected promise.
  240.  
  241. @method reject
  242. @param {Any} reason Reason for the rejection of this promise. Usually an Error
  243. Object
  244. @return {Promise} A rejected promise
  245. @static
  246. **/
  247. Promise.reject = function (reason) {
  248. /*jshint newcap: false */
  249. return new this(function (resolve, reject) {
  250. /*jshint newcap: true */
  251. reject(reason);
  252. });
  253. };
  254.  
  255. /**
  256. Returns a promise that is resolved or rejected when all values are resolved or
  257. any is rejected. This is useful for waiting for the resolution of multiple
  258. promises, such as reading multiple files in Node.js or making multiple XHR
  259. requests in the browser.
  260.  
  261. @method all
  262. @param {Any[]} values An array of any kind of values, promises or not. If a value is not
  263. @return [Promise] A promise for an array of all the fulfillment values
  264. @static
  265. **/
  266. Promise.all = function (values) {
  267. var Promise = this;
  268. return new Promise(function (resolve, reject) {
  269. if (!Lang.isArray(values)) {
  270. reject(new TypeError('Promise.all expects an array of values or promises'));
  271. return;
  272. }
  273.  
  274. var remaining = values.length,
  275. i = 0,
  276. length = values.length,
  277. results = [];
  278.  
  279. function oneDone(index) {
  280. return function (value) {
  281. results[index] = value;
  282.  
  283. remaining--;
  284.  
  285. if (!remaining) {
  286. resolve(results);
  287. }
  288. };
  289. }
  290.  
  291. if (length < 1) {
  292. return resolve(results);
  293. }
  294.  
  295. for (; i < length; i++) {
  296. Promise.resolve(values[i]).then(oneDone(i), reject);
  297. }
  298. });
  299. };
  300.  
  301. /**
  302. Returns a promise that is resolved or rejected when any of values is either
  303. resolved or rejected. Can be used for providing early feedback in the UI
  304. while other operations are still pending.
  305.  
  306. @method race
  307. @param {Any[]} values An array of values or promises
  308. @return {Promise}
  309. @static
  310. **/
  311. Promise.race = function (values) {
  312. var Promise = this;
  313. return new Promise(function (resolve, reject) {
  314. if (!Lang.isArray(values)) {
  315. reject(new TypeError('Promise.race expects an array of values or promises'));
  316. return;
  317. }
  318.  
  319. // just go through the list and resolve and reject at the first change
  320. // This abuses the fact that calling resolve/reject multiple times
  321. // doesn't change the state of the returned promise
  322. for (var i = 0, count = values.length; i < count; i++) {
  323. Promise.resolve(values[i]).then(resolve, reject);
  324. }
  325. });
  326. };
  327.  
  328. Y.Promise = Promise;
  329.