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