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) {
|
12 | define(function(require) {
|
13 |
|
14 | var when = require('./when');
|
15 | var Promise = when.Promise;
|
16 | var _liftAll = require('./lib/liftAll');
|
17 | var setTimer = require('./lib/env').setTimer;
|
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 |
|