node.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. /** @license MIT License (c) copyright 2013 original author or authors */
  2. /**
  3. * Collection of helpers for interfacing with node-style asynchronous functions
  4. * using promises.
  5. *
  6. * @author Brian Cavalier
  7. * @contributor Renato Zannon
  8. */
  9. (function(define) {
  10. define(function(require) {
  11. var when = require('./when');
  12. var _liftAll = require('./lib/liftAll');
  13. var setTimer = require('./lib/env').setTimer;
  14. var slice = Array.prototype.slice;
  15. var _apply = require('./lib/apply')(when.Promise, dispatch);
  16. return {
  17. lift: lift,
  18. liftAll: liftAll,
  19. apply: apply,
  20. call: call,
  21. createCallback: createCallback,
  22. bindCallback: bindCallback,
  23. liftCallback: liftCallback
  24. };
  25. /**
  26. * Takes a node-style async function and calls it immediately (with an optional
  27. * array of arguments or promises for arguments). It returns a promise whose
  28. * resolution depends on whether the async functions calls its callback with the
  29. * conventional error argument or not.
  30. *
  31. * With this it becomes possible to leverage existing APIs while still reaping
  32. * the benefits of promises.
  33. *
  34. * @example
  35. * function onlySmallNumbers(n, callback) {
  36. * if(n < 10) {
  37. * callback(null, n + 10);
  38. * } else {
  39. * callback(new Error("Calculation failed"));
  40. * }
  41. * }
  42. *
  43. * var nodefn = require("when/node/function");
  44. *
  45. * // Logs '15'
  46. * nodefn.apply(onlySmallNumbers, [5]).then(console.log, console.error);
  47. *
  48. * // Logs 'Calculation failed'
  49. * nodefn.apply(onlySmallNumbers, [15]).then(console.log, console.error);
  50. *
  51. * @param {function} f node-style function that will be called
  52. * @param {Array} [args] array of arguments to func
  53. * @returns {Promise} promise for the value func passes to its callback
  54. */
  55. function apply(f, args) {
  56. return _apply(f, this, args || []);
  57. }
  58. function dispatch(f, thisArg, args, h) {
  59. var cb = createCallback(h);
  60. try {
  61. switch(args.length) {
  62. case 2: f.call(thisArg, args[0], args[1], cb); break;
  63. case 1: f.call(thisArg, args[0], cb); break;
  64. case 0: f.call(thisArg, cb); break;
  65. default:
  66. args.push(cb);
  67. f.apply(thisArg, args);
  68. }
  69. } catch(e) {
  70. h.reject(e);
  71. }
  72. }
  73. /**
  74. * Has the same behavior that {@link apply} has, with the difference that the
  75. * arguments to the function are provided individually, while {@link apply} accepts
  76. * a single array.
  77. *
  78. * @example
  79. * function sumSmallNumbers(x, y, callback) {
  80. * var result = x + y;
  81. * if(result < 10) {
  82. * callback(null, result);
  83. * } else {
  84. * callback(new Error("Calculation failed"));
  85. * }
  86. * }
  87. *
  88. * // Logs '5'
  89. * nodefn.call(sumSmallNumbers, 2, 3).then(console.log, console.error);
  90. *
  91. * // Logs 'Calculation failed'
  92. * nodefn.call(sumSmallNumbers, 5, 10).then(console.log, console.error);
  93. *
  94. * @param {function} f node-style function that will be called
  95. * @param {...*} [args] arguments that will be forwarded to the function
  96. * @returns {Promise} promise for the value func passes to its callback
  97. */
  98. function call(f /*, args... */) {
  99. return _apply(f, this, slice.call(arguments, 1));
  100. }
  101. /**
  102. * Takes a node-style function and returns new function that wraps the
  103. * original and, instead of taking a callback, returns a promise. Also, it
  104. * knows how to handle promises given as arguments, waiting for their
  105. * resolution before executing.
  106. *
  107. * Upon execution, the orginal function is executed as well. If it passes
  108. * a truthy value as the first argument to the callback, it will be
  109. * interpreted as an error condition, and the promise will be rejected
  110. * with it. Otherwise, the call is considered a resolution, and the promise
  111. * is resolved with the callback's second argument.
  112. *
  113. * @example
  114. * var fs = require("fs"), nodefn = require("when/node/function");
  115. *
  116. * var promiseRead = nodefn.lift(fs.readFile);
  117. *
  118. * // The promise is resolved with the contents of the file if everything
  119. * // goes ok
  120. * promiseRead('exists.txt').then(console.log, console.error);
  121. *
  122. * // And will be rejected if something doesn't work out
  123. * // (e.g. the files does not exist)
  124. * promiseRead('doesnt_exist.txt').then(console.log, console.error);
  125. *
  126. *
  127. * @param {Function} f node-style function to be lifted
  128. * @param {...*} [args] arguments to be prepended for the new function @deprecated
  129. * @returns {Function} a promise-returning function
  130. */
  131. function lift(f /*, args... */) {
  132. var args1 = arguments.length > 1 ? slice.call(arguments, 1) : [];
  133. return function() {
  134. // TODO: Simplify once partialing has been removed
  135. var l = args1.length;
  136. var al = arguments.length;
  137. var args = new Array(al + l);
  138. var i;
  139. for(i=0; i<l; ++i) {
  140. args[i] = args1[i];
  141. }
  142. for(i=0; i<al; ++i) {
  143. args[i+l] = arguments[i];
  144. }
  145. return _apply(f, this, args);
  146. };
  147. }
  148. /**
  149. * Lift all the functions/methods on src
  150. * @param {object|function} src source whose functions will be lifted
  151. * @param {function?} combine optional function for customizing the lifting
  152. * process. It is passed dst, the lifted function, and the property name of
  153. * the original function on src.
  154. * @param {(object|function)?} dst option destination host onto which to place lifted
  155. * functions. If not provided, liftAll returns a new object.
  156. * @returns {*} If dst is provided, returns dst with lifted functions as
  157. * properties. If dst not provided, returns a new object with lifted functions.
  158. */
  159. function liftAll(src, combine, dst) {
  160. return _liftAll(lift, combine, dst, src);
  161. }
  162. /**
  163. * Takes an object that responds to the resolver interface, and returns
  164. * a function that will resolve or reject it depending on how it is called.
  165. *
  166. * @example
  167. * function callbackTakingFunction(callback) {
  168. * if(somethingWrongHappened) {
  169. * callback(error);
  170. * } else {
  171. * callback(null, interestingValue);
  172. * }
  173. * }
  174. *
  175. * var when = require('when'), nodefn = require('when/node/function');
  176. *
  177. * var deferred = when.defer();
  178. * callbackTakingFunction(nodefn.createCallback(deferred.resolver));
  179. *
  180. * deferred.promise.then(function(interestingValue) {
  181. * // Use interestingValue
  182. * });
  183. *
  184. * @param {Resolver} resolver that will be 'attached' to the callback
  185. * @returns {Function} a node-style callback function
  186. */
  187. function createCallback(resolver) {
  188. return function(err, value) {
  189. if(err) {
  190. resolver.reject(err);
  191. } else if(arguments.length > 2) {
  192. resolver.resolve(slice.call(arguments, 1));
  193. } else {
  194. resolver.resolve(value);
  195. }
  196. };
  197. }
  198. /**
  199. * Attaches a node-style callback to a promise, ensuring the callback is
  200. * called for either fulfillment or rejection. Returns a promise with the same
  201. * state as the passed-in promise.
  202. *
  203. * @example
  204. * var deferred = when.defer();
  205. *
  206. * function callback(err, value) {
  207. * // Handle err or use value
  208. * }
  209. *
  210. * bindCallback(deferred.promise, callback);
  211. *
  212. * deferred.resolve('interesting value');
  213. *
  214. * @param {Promise} promise The promise to be attached to.
  215. * @param {Function} callback The node-style callback to attach.
  216. * @returns {Promise} A promise with the same state as the passed-in promise.
  217. */
  218. function bindCallback(promise, callback) {
  219. promise = when(promise);
  220. if (callback) {
  221. promise.then(success, wrapped);
  222. }
  223. return promise;
  224. function success(value) {
  225. wrapped(null, value);
  226. }
  227. function wrapped(err, value) {
  228. setTimer(function () {
  229. callback(err, value);
  230. }, 0);
  231. }
  232. }
  233. /**
  234. * Takes a node-style callback and returns new function that accepts a
  235. * promise, calling the original callback when the promise is either
  236. * fulfilled or rejected with the appropriate arguments.
  237. *
  238. * @example
  239. * var deferred = when.defer();
  240. *
  241. * function callback(err, value) {
  242. * // Handle err or use value
  243. * }
  244. *
  245. * var wrapped = liftCallback(callback);
  246. *
  247. * // `wrapped` can now be passed around at will
  248. * wrapped(deferred.promise);
  249. *
  250. * deferred.resolve('interesting value');
  251. *
  252. * @param {Function} callback The node-style callback to wrap.
  253. * @returns {Function} The lifted, promise-accepting function.
  254. */
  255. function liftCallback(callback) {
  256. return function(promise) {
  257. return bindCallback(promise, callback);
  258. };
  259. }
  260. });
  261. })(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });