promise.js 11 KB

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