UNPKG

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