Promise.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. (function(global) {
  2. global.createPromiseCapability = function() {
  3. var promiseCapability = {};
  4. var promise = new Promise(function(resolve, reject) {
  5. promiseCapability.resolve = resolve;
  6. promiseCapability.reject = reject;
  7. });
  8. promiseCapability.promise = promise;
  9. return promiseCapability;
  10. };
  11. //
  12. // Check for native Promise and it has correct interface
  13. //
  14. var NativePromise = global['Promise'];
  15. var nativePromiseSupported =
  16. NativePromise &&
  17. // Some of these methods are missing from
  18. // Firefox/Chrome experimental implementations
  19. 'resolve' in NativePromise &&
  20. 'reject' in NativePromise &&
  21. 'all' in NativePromise &&
  22. 'race' in NativePromise &&
  23. // Older version of the spec had a resolver object
  24. // as the arg rather than a function
  25. (function() {
  26. var resolve;
  27. new NativePromise(function(r) {
  28. resolve = r;
  29. });
  30. return typeof resolve === 'function';
  31. })();
  32. //
  33. // export if necessary
  34. //
  35. if (typeof exports !== 'undefined' && exports) {
  36. // node.js
  37. exports.Promise = nativePromiseSupported ? NativePromise : Promise;
  38. exports.Polyfill = Promise;
  39. } else {
  40. // AMD
  41. if (typeof define == 'function' && define.amd) {
  42. define(function() {
  43. return nativePromiseSupported ? NativePromise : Promise;
  44. });
  45. } else {
  46. // in browser add to global
  47. if (!nativePromiseSupported)
  48. global['Promise'] = Promise;
  49. }
  50. }
  51. //
  52. // Polyfill
  53. //
  54. var PENDING = 'pending';
  55. var SEALED = 'sealed';
  56. var FULFILLED = 'fulfilled';
  57. var REJECTED = 'rejected';
  58. var NOOP = function() {};
  59. function isArray(value) {
  60. return Object.prototype.toString.call(value) === '[object Array]';
  61. }
  62. // async calls
  63. var asyncSetTimer = typeof setImmediate !== 'undefined' ? setImmediate : setTimeout;
  64. var asyncQueue = [];
  65. var asyncTimer;
  66. function asyncFlush() {
  67. // run promise callbacks
  68. for (var i = 0; i < asyncQueue.length; i++)
  69. asyncQueue[i][0](asyncQueue[i][1]);
  70. // reset async asyncQueue
  71. asyncQueue = [];
  72. asyncTimer = false;
  73. }
  74. function asyncCall(callback, arg) {
  75. asyncQueue.push([callback, arg]);
  76. if (!asyncTimer) {
  77. asyncTimer = true;
  78. asyncSetTimer(asyncFlush, 0);
  79. }
  80. }
  81. function invokeResolver(resolver, promise) {
  82. function resolvePromise(value) {
  83. resolve(promise, value);
  84. }
  85. function rejectPromise(reason) {
  86. reject(promise, reason);
  87. }
  88. try {
  89. resolver(resolvePromise, rejectPromise);
  90. } catch (e) {
  91. rejectPromise(e);
  92. }
  93. }
  94. function invokeCallback(subscriber) {
  95. var owner = subscriber.owner;
  96. var settled = owner.state_;
  97. var value = owner.data_;
  98. var callback = subscriber[settled];
  99. var promise = subscriber.then;
  100. if (typeof callback === 'function') {
  101. settled = FULFILLED;
  102. try {
  103. value = callback(value);
  104. } catch (e) {
  105. reject(promise, e);
  106. }
  107. }
  108. if (!handleThenable(promise, value)) {
  109. if (settled === FULFILLED)
  110. resolve(promise, value);
  111. if (settled === REJECTED)
  112. reject(promise, value);
  113. }
  114. }
  115. function handleThenable(promise, value) {
  116. var resolved;
  117. try {
  118. if (promise === value)
  119. throw new TypeError('A promises callback cannot return that same promise.');
  120. if (value && (typeof value === 'function' || typeof value === 'object')) {
  121. var then = value.then; // then should be retrived only once
  122. if (typeof then === 'function') {
  123. then.call(value, function(val) {
  124. if (!resolved) {
  125. resolved = true;
  126. if (value !== val)
  127. resolve(promise, val);
  128. else
  129. fulfill(promise, val);
  130. }
  131. }, function(reason) {
  132. if (!resolved) {
  133. resolved = true;
  134. reject(promise, reason);
  135. }
  136. });
  137. return true;
  138. }
  139. }
  140. } catch (e) {
  141. if (!resolved)
  142. reject(promise, e);
  143. return true;
  144. }
  145. return false;
  146. }
  147. function resolve(promise, value) {
  148. if (promise === value || !handleThenable(promise, value))
  149. fulfill(promise, value);
  150. }
  151. function fulfill(promise, value) {
  152. if (promise.state_ === PENDING) {
  153. promise.state_ = SEALED;
  154. promise.data_ = value;
  155. asyncCall(publishFulfillment, promise);
  156. }
  157. }
  158. function reject(promise, reason) {
  159. if (promise.state_ === PENDING) {
  160. promise.state_ = SEALED;
  161. promise.data_ = reason;
  162. asyncCall(publishRejection, promise);
  163. }
  164. }
  165. function publish(promise) {
  166. var callbacks = promise.then_;
  167. promise.then_ = undefined;
  168. for (var i = 0; i < callbacks.length; i++) {
  169. invokeCallback(callbacks[i]);
  170. }
  171. }
  172. function publishFulfillment(promise) {
  173. promise.state_ = FULFILLED;
  174. publish(promise);
  175. }
  176. function publishRejection(promise) {
  177. promise.state_ = REJECTED;
  178. publish(promise);
  179. }
  180. /**
  181. * @class
  182. */
  183. function Promise(resolver) {
  184. if (typeof resolver !== 'function')
  185. throw new TypeError('Promise constructor takes a function argument');
  186. if (this instanceof Promise === false)
  187. throw new TypeError('Failed to construct \'Promise\': Please use the \'new\' operator, this object constructor cannot be called as a function.');
  188. this.then_ = [];
  189. invokeResolver(resolver, this);
  190. }
  191. Promise.prototype = {
  192. constructor: Promise,
  193. state_: PENDING,
  194. then_: null,
  195. data_: undefined,
  196. then: function(onFulfillment, onRejection) {
  197. var subscriber = {
  198. owner: this,
  199. then: new this.constructor(NOOP),
  200. fulfilled: onFulfillment,
  201. rejected: onRejection
  202. };
  203. if (this.state_ === FULFILLED || this.state_ === REJECTED) {
  204. // already resolved, call callback async
  205. asyncCall(invokeCallback, subscriber);
  206. } else {
  207. // subscribe
  208. this.then_.push(subscriber);
  209. }
  210. return subscriber.then;
  211. },
  212. 'catch': function(onRejection) {
  213. return this.then(null, onRejection);
  214. }
  215. };
  216. Promise.all = function(promises) {
  217. var Class = this;
  218. if (!isArray(promises))
  219. throw new TypeError('You must pass an array to Promise.all().');
  220. return new Class(function(resolve, reject) {
  221. var results = [];
  222. var remaining = 0;
  223. function resolver(index) {
  224. remaining++;
  225. return function(value) {
  226. results[index] = value;
  227. if (!--remaining)
  228. resolve(results);
  229. };
  230. }
  231. for (var i = 0, promise; i < promises.length; i++) {
  232. promise = promises[i];
  233. if (promise && typeof promise.then === 'function')
  234. promise.then(resolver(i), reject);
  235. else
  236. results[i] = promise;
  237. }
  238. if (!remaining)
  239. resolve(results);
  240. });
  241. };
  242. Promise.race = function(promises) {
  243. var Class = this;
  244. if (!isArray(promises))
  245. throw new TypeError('You must pass an array to Promise.race().');
  246. return new Class(function(resolve, reject) {
  247. for (var i = 0, promise; i < promises.length; i++) {
  248. promise = promises[i];
  249. if (promise && typeof promise.then === 'function')
  250. promise.then(resolve, reject);
  251. else
  252. resolve(promise);
  253. }
  254. });
  255. };
  256. Promise.resolve = function(value) {
  257. var Class = this;
  258. if (value && typeof value === 'object' && value.constructor === Class)
  259. return value;
  260. return new Class(function(resolve) {
  261. resolve(value);
  262. });
  263. };
  264. Promise.reject = function(reason) {
  265. var Class = this;
  266. return new Class(function(resolve, reject) {
  267. reject(reason);
  268. });
  269. };
  270. if (!nativePromiseSupported) {
  271. global.Promise = Promise;
  272. }
  273. })(typeof window != 'undefined' ? window : typeof global != 'undefined' ? global : typeof self != 'undefined' ? self : this);