UNPKG

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