promise.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. 'use strict';
  2. var util = require('util');
  3. var EventEmitter = require('events').EventEmitter;
  4. function toArray(arr, start, end) {
  5. return Array.prototype.slice.call(arr, start, end)
  6. }
  7. function strongUnshift(x, arrLike) {
  8. var arr = toArray(arrLike);
  9. arr.unshift(x);
  10. return arr;
  11. }
  12. /**
  13. * Promise constructor.
  14. *
  15. * _NOTE: The success and failure event names can be overridden by setting `Promise.SUCCESS` and `Promise.FAILURE` respectively._
  16. *
  17. * @param {Function} back a function that accepts `fn(err, ...){}` as signature
  18. * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
  19. * @event `reject`: Emits when the promise is rejected (event name may be overridden)
  20. * @event `fulfill`: Emits when the promise is fulfilled (event name may be overridden)
  21. * @api public
  22. */
  23. function Promise(back) {
  24. this.emitter = new EventEmitter();
  25. this.emitted = {};
  26. this.ended = false;
  27. if ('function' == typeof back)
  28. this.onResolve(back);
  29. }
  30. /*
  31. * Module exports.
  32. */
  33. module.exports = Promise;
  34. /*!
  35. * event names
  36. */
  37. Promise.SUCCESS = 'fulfill';
  38. Promise.FAILURE = 'reject';
  39. /**
  40. * Adds `listener` to the `event`.
  41. *
  42. * If `event` is either the success or failure event and the event has already been emitted, the`listener` is called immediately and passed the results of the original emitted event.
  43. *
  44. * @param {String} event
  45. * @param {Function} callback
  46. * @return {Promise} this
  47. * @api private
  48. */
  49. Promise.prototype.on = function (event, callback) {
  50. if (this.emitted[event])
  51. callback.apply(undefined, this.emitted[event]);
  52. else
  53. this.emitter.on(event, callback);
  54. return this;
  55. };
  56. /**
  57. * Keeps track of emitted events to run them on `on`.
  58. *
  59. * @api private
  60. */
  61. Promise.prototype.safeEmit = function (event) {
  62. // ensures a promise can't be fulfill() or reject() more than once
  63. if (event == Promise.SUCCESS || event == Promise.FAILURE) {
  64. if (this.emitted[Promise.SUCCESS] || this.emitted[Promise.FAILURE]) {
  65. return this;
  66. }
  67. this.emitted[event] = toArray(arguments, 1);
  68. }
  69. this.emitter.emit.apply(this.emitter, arguments);
  70. return this;
  71. };
  72. /**
  73. * Fulfills this promise with passed arguments.
  74. *
  75. * If this promise has already been fulfilled or rejected, no action is taken.
  76. *
  77. * @api public
  78. */
  79. Promise.prototype.fulfill = function () {
  80. return this.safeEmit.apply(this, strongUnshift(Promise.SUCCESS, arguments));
  81. };
  82. /**
  83. * Rejects this promise with `reason`.
  84. *
  85. * If this promise has already been fulfilled or rejected, no action is taken.
  86. *
  87. * @api public
  88. * @param {Object|String} reason
  89. * @return {Promise} this
  90. */
  91. Promise.prototype.reject = function (reason) {
  92. if (this.ended && !this.hasRejectListeners()) throw reason;
  93. return this.safeEmit(Promise.FAILURE, reason);
  94. };
  95. /**
  96. * Resolves this promise to a rejected state if `err` is passed or
  97. * fulfilled state if no `err` is passed.
  98. *
  99. * @param {Error} [err] error or null
  100. * @param {Object} [val] value to fulfill the promise with
  101. * @api public
  102. */
  103. Promise.prototype.resolve = function (err, val) {
  104. if (err) return this.reject(err);
  105. return this.fulfill(val);
  106. };
  107. /**
  108. * Adds a listener to the SUCCESS event.
  109. *
  110. * @return {Promise} this
  111. * @api public
  112. */
  113. Promise.prototype.onFulfill = function (fn) {
  114. if (!fn) return this;
  115. if ('function' != typeof fn) throw new TypeError("fn should be a function");
  116. return this.on(Promise.SUCCESS, fn);
  117. };
  118. Promise.prototype.hasRejectListeners = function () {
  119. return this.emitter.listeners(Promise.FAILURE).length > 0;
  120. };
  121. /**
  122. * Adds a listener to the FAILURE event.
  123. *
  124. * @return {Promise} this
  125. * @api public
  126. */
  127. Promise.prototype.onReject = function (fn) {
  128. if (!fn) return this;
  129. if ('function' != typeof fn) throw new TypeError("fn should be a function");
  130. return this.on(Promise.FAILURE, fn);
  131. };
  132. /**
  133. * Adds a single function as a listener to both SUCCESS and FAILURE.
  134. *
  135. * It will be executed with traditional node.js argument position:
  136. * function (err, args...) {}
  137. *
  138. * Also marks the promise as `end`ed, since it's the common use-case, and yet has no
  139. * side effects unless `fn` is undefined or null.
  140. *
  141. * @param {Function} fn
  142. * @return {Promise} this
  143. */
  144. Promise.prototype.onResolve = function (fn) {
  145. this.end();
  146. if (!fn) return this;
  147. if ('function' != typeof fn) throw new TypeError("fn should be a function");
  148. this.on(Promise.FAILURE, function (err) { fn.call(this, err); });
  149. this.on(Promise.SUCCESS, function () { fn.apply(this, strongUnshift(null, arguments)); });
  150. return this;
  151. };
  152. /**
  153. * Creates a new promise and returns it. If `onFulfill` or
  154. * `onReject` are passed, they are added as SUCCESS/ERROR callbacks
  155. * to this promise after the next tick.
  156. *
  157. * Conforms to [promises/A+](https://github.com/promises-aplus/promises-spec) specification. Read for more detail how to use this method.
  158. *
  159. * ####Example:
  160. *
  161. * var p = new Promise;
  162. * p.then(function (arg) {
  163. * return arg + 1;
  164. * }).then(function (arg) {
  165. * throw new Error(arg + ' is an error!');
  166. * }).then(null, function (err) {
  167. * assert.ok(err instanceof Error);
  168. * assert.equal('2 is an error', err.message);
  169. * });
  170. * p.complete(1);
  171. *
  172. * @see promises-A+ https://github.com/promises-aplus/promises-spec
  173. * @param {Function} onFulfill
  174. * @param {Function} [onReject]
  175. * @return {Promise} newPromise
  176. */
  177. Promise.prototype.then = function (onFulfill, onReject) {
  178. var newPromise = new Promise;
  179. if ('function' == typeof onFulfill) {
  180. this.onFulfill(handler(newPromise, onFulfill));
  181. } else {
  182. this.onFulfill(newPromise.fulfill.bind(newPromise));
  183. }
  184. if ('function' == typeof onReject) {
  185. this.onReject(handler(newPromise, onReject));
  186. } else {
  187. this.onReject(newPromise.reject.bind(newPromise));
  188. }
  189. return newPromise;
  190. };
  191. function handler(promise, fn) {
  192. function newTickHandler() {
  193. var pDomain = promise.emitter.domain;
  194. if (pDomain && pDomain !== process.domain) pDomain.enter();
  195. try {
  196. var x = fn.apply(undefined, boundHandler.args);
  197. } catch (err) {
  198. promise.reject(err);
  199. return;
  200. }
  201. resolve(promise, x);
  202. }
  203. function boundHandler() {
  204. boundHandler.args = arguments;
  205. process.nextTick(newTickHandler);
  206. }
  207. return boundHandler;
  208. }
  209. function resolve(promise, x) {
  210. function fulfillOnce() {
  211. if (done++) return;
  212. resolve.apply(undefined, strongUnshift(promise, arguments));
  213. }
  214. function rejectOnce(reason) {
  215. if (done++) return;
  216. promise.reject(reason);
  217. }
  218. if (promise === x) {
  219. promise.reject(new TypeError("promise and x are the same"));
  220. return;
  221. }
  222. var rest = toArray(arguments, 1);
  223. var type = typeof x;
  224. if ('undefined' == type || null == x || !('object' == type || 'function' == type)) {
  225. promise.fulfill.apply(promise, rest);
  226. return;
  227. }
  228. try {
  229. var theThen = x.then;
  230. } catch (err) {
  231. promise.reject(err);
  232. return;
  233. }
  234. if ('function' != typeof theThen) {
  235. promise.fulfill.apply(promise, rest);
  236. return;
  237. }
  238. var done = 0;
  239. try {
  240. var ret = theThen.call(x, fulfillOnce, rejectOnce);
  241. return ret;
  242. } catch (err) {
  243. if (done++) return;
  244. promise.reject(err);
  245. }
  246. }
  247. /**
  248. * Signifies that this promise was the last in a chain of `then()s`: if a handler passed to the call to `then` which produced this promise throws, the exception will go uncaught.
  249. *
  250. * ####Example:
  251. *
  252. * var p = new Promise;
  253. * p.then(function(){ throw new Error('shucks') });
  254. * setTimeout(function () {
  255. * p.fulfill();
  256. * // error was caught and swallowed by the promise returned from
  257. * // p.then(). we either have to always register handlers on
  258. * // the returned promises or we can do the following...
  259. * }, 10);
  260. *
  261. * // this time we use .end() which prevents catching thrown errors
  262. * var p = new Promise;
  263. * var p2 = p.then(function(){ throw new Error('shucks') }).end(); // <--
  264. * setTimeout(function () {
  265. * p.fulfill(); // throws "shucks"
  266. * }, 10);
  267. *
  268. * @api public
  269. * @param {Function} [onReject]
  270. * @return {Promise} this
  271. */
  272. Promise.prototype.end = function (onReject) {
  273. this.onReject(onReject);
  274. this.ended = true;
  275. return this;
  276. };
  277. /**
  278. * A debug utility function that adds handlers to a promise that will log some output to the `console`
  279. *
  280. * ####Example:
  281. *
  282. * var p = new Promise;
  283. * p.then(function(){ throw new Error('shucks') });
  284. * setTimeout(function () {
  285. * p.fulfill();
  286. * // error was caught and swallowed by the promise returned from
  287. * // p.then(). we either have to always register handlers on
  288. * // the returned promises or we can do the following...
  289. * }, 10);
  290. *
  291. * // this time we use .end() which prevents catching thrown errors
  292. * var p = new Promise;
  293. * var p2 = p.then(function(){ throw new Error('shucks') }).end(); // <--
  294. * setTimeout(function () {
  295. * p.fulfill(); // throws "shucks"
  296. * }, 10);
  297. *
  298. * @api public
  299. * @param {Promise} p
  300. * @param {String} name
  301. * @return {Promise} this
  302. */
  303. Promise.trace = function (p, name) {
  304. p.then(
  305. function () {
  306. console.log("%s fulfill %j", name, toArray(arguments));
  307. },
  308. function () {
  309. console.log("%s reject %j", name, toArray(arguments));
  310. }
  311. )
  312. };
  313. Promise.prototype.chain = function (p2) {
  314. var p1 = this;
  315. p1.onFulfill(p2.fulfill.bind(p2));
  316. p1.onReject(p2.reject.bind(p2));
  317. return p2;
  318. };
  319. Promise.prototype.all = function (promiseOfArr) {
  320. var pRet = new Promise;
  321. this.then(promiseOfArr).then(
  322. function (promiseArr) {
  323. var count = 0;
  324. var ret = [];
  325. var errSentinel;
  326. if (!promiseArr.length) pRet.resolve();
  327. promiseArr.forEach(function (promise, index) {
  328. if (errSentinel) return;
  329. count++;
  330. promise.then(
  331. function (val) {
  332. if (errSentinel) return;
  333. ret[index] = val;
  334. --count;
  335. if (count == 0) pRet.fulfill(ret);
  336. },
  337. function (err) {
  338. if (errSentinel) return;
  339. errSentinel = err;
  340. pRet.reject(err);
  341. }
  342. );
  343. });
  344. return pRet;
  345. }
  346. , pRet.reject.bind(pRet)
  347. );
  348. return pRet;
  349. };
  350. Promise.hook = function (arr) {
  351. var p1 = new Promise;
  352. var pFinal = new Promise;
  353. var signalP = function () {
  354. --count;
  355. if (count == 0)
  356. pFinal.fulfill();
  357. return pFinal;
  358. };
  359. var count = 1;
  360. var ps = p1;
  361. arr.forEach(function (hook) {
  362. ps = ps.then(
  363. function () {
  364. var p = new Promise;
  365. count++;
  366. hook(p.resolve.bind(p), signalP);
  367. return p;
  368. }
  369. )
  370. });
  371. ps = ps.then(signalP);
  372. p1.resolve();
  373. return ps;
  374. };
  375. /* This is for the A+ tests, but it's very useful as well */
  376. Promise.fulfilled = function fulfilled() { var p = new Promise; p.fulfill.apply(p, arguments); return p; };
  377. Promise.rejected = function rejected(reason) { return new Promise().reject(reason); };
  378. Promise.deferred = function deferred() {
  379. var p = new Promise;
  380. return {
  381. promise: p,
  382. reject: p.reject.bind(p),
  383. resolve: p.fulfill.bind(p),
  384. callback: p.resolve.bind(p)
  385. }
  386. };
  387. /* End A+ tests adapter bit */