UNPKG

8.39 kBJavaScriptView Raw
1/** @license MIT License (c) copyright 2013-2014 original author or authors */
2
3/**
4 * Collection of helper functions for interacting with 'traditional',
5 * callback-taking functions using a promise interface.
6 *
7 * @author Renato Zannon
8 * @contributor Brian Cavalier
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 slice = Array.prototype.slice;
18
19 return {
20 lift: lift,
21 liftAll: liftAll,
22 apply: apply,
23 call: call,
24 promisify: promisify
25 };
26
27 /**
28 * Takes a `traditional` callback-taking function and returns a promise for its
29 * result, accepting an optional array of arguments (that might be values or
30 * promises). It assumes that the function takes its callback and errback as
31 * the last two arguments. The resolution of the promise depends on whether the
32 * function will call its callback or its errback.
33 *
34 * @example
35 * var domIsLoaded = callbacks.apply($);
36 * domIsLoaded.then(function() {
37 * doMyDomStuff();
38 * });
39 *
40 * @example
41 * function existingAjaxyFunction(url, callback, errback) {
42 * // Complex logic you'd rather not change
43 * }
44 *
45 * var promise = callbacks.apply(existingAjaxyFunction, ["/movies.json"]);
46 *
47 * promise.then(function(movies) {
48 * // Work with movies
49 * }, function(reason) {
50 * // Handle error
51 * });
52 *
53 * @param {function} asyncFunction function to be called
54 * @param {Array} [extraAsyncArgs] array of arguments to asyncFunction
55 * @returns {Promise} promise for the callback value of asyncFunction
56 */
57 function apply(asyncFunction, extraAsyncArgs) {
58 return _apply(asyncFunction, this, extraAsyncArgs || []);
59 }
60
61 /**
62 * Apply helper that allows specifying thisArg
63 * @private
64 */
65 function _apply(asyncFunction, thisArg, extraAsyncArgs) {
66 return Promise.all(extraAsyncArgs).then(function(args) {
67 var p = Promise._defer();
68 args.push(alwaysUnary(p._handler.resolve, p._handler),
69 alwaysUnary(p._handler.reject, p._handler));
70 asyncFunction.apply(thisArg, args);
71
72 return p;
73 });
74 }
75
76 /**
77 * Works as `callbacks.apply` does, with the difference that the arguments to
78 * the function are passed individually, instead of as an array.
79 *
80 * @example
81 * function sumInFiveSeconds(a, b, callback) {
82 * setTimeout(function() {
83 * callback(a + b);
84 * }, 5000);
85 * }
86 *
87 * var sumPromise = callbacks.call(sumInFiveSeconds, 5, 10);
88 *
89 * // Logs '15' 5 seconds later
90 * sumPromise.then(console.log);
91 *
92 * @param {function} asyncFunction function to be called
93 * @param {...*} args arguments that will be forwarded to the function
94 * @returns {Promise} promise for the callback value of asyncFunction
95 */
96 function call(asyncFunction/*, arg1, arg2...*/) {
97 return _apply(asyncFunction, this, slice.call(arguments, 1));
98 }
99
100 /**
101 * Takes a 'traditional' callback/errback-taking function and returns a function
102 * that returns a promise instead. The resolution/rejection of the promise
103 * depends on whether the original function will call its callback or its
104 * errback.
105 *
106 * If additional arguments are passed to the `lift` call, they will be prepended
107 * on the calls to the original function, much like `Function.prototype.bind`.
108 *
109 * The resulting function is also "promise-aware", in the sense that, if given
110 * promises as arguments, it will wait for their resolution before executing.
111 *
112 * @example
113 * function traditionalAjax(method, url, callback, errback) {
114 * var xhr = new XMLHttpRequest();
115 * xhr.open(method, url);
116 *
117 * xhr.onload = callback;
118 * xhr.onerror = errback;
119 *
120 * xhr.send();
121 * }
122 *
123 * var promiseAjax = callbacks.lift(traditionalAjax);
124 * promiseAjax("GET", "/movies.json").then(console.log, console.error);
125 *
126 * var promiseAjaxGet = callbacks.lift(traditionalAjax, "GET");
127 * promiseAjaxGet("/movies.json").then(console.log, console.error);
128 *
129 * @param {Function} f traditional async function to be decorated
130 * @param {...*} [args] arguments to be prepended for the new function @deprecated
131 * @returns {Function} a promise-returning function
132 */
133 function lift(f/*, args...*/) {
134 var args = arguments.length > 1 ? slice.call(arguments, 1) : [];
135 return function() {
136 return _apply(f, this, args.concat(slice.call(arguments)));
137 };
138 }
139
140 /**
141 * Lift all the functions/methods on src
142 * @param {object|function} src source whose functions will be lifted
143 * @param {function?} combine optional function for customizing the lifting
144 * process. It is passed dst, the lifted function, and the property name of
145 * the original function on src.
146 * @param {(object|function)?} dst option destination host onto which to place lifted
147 * functions. If not provided, liftAll returns a new object.
148 * @returns {*} If dst is provided, returns dst with lifted functions as
149 * properties. If dst not provided, returns a new object with lifted functions.
150 */
151 function liftAll(src, combine, dst) {
152 return _liftAll(lift, combine, dst, src);
153 }
154
155 /**
156 * `promisify` is a version of `lift` that allows fine-grained control over the
157 * arguments that passed to the underlying function. It is intended to handle
158 * functions that don't follow the common callback and errback positions.
159 *
160 * The control is done by passing an object whose 'callback' and/or 'errback'
161 * keys, whose values are the corresponding 0-based indexes of the arguments on
162 * the function. Negative values are interpreted as being relative to the end
163 * of the arguments array.
164 *
165 * If arguments are given on the call to the 'promisified' function, they are
166 * intermingled with the callback and errback. If a promise is given among them,
167 * the execution of the function will only occur after its resolution.
168 *
169 * @example
170 * var delay = callbacks.promisify(setTimeout, {
171 * callback: 0
172 * });
173 *
174 * delay(100).then(function() {
175 * console.log("This happens 100ms afterwards");
176 * });
177 *
178 * @example
179 * function callbackAsLast(errback, followsStandards, callback) {
180 * if(followsStandards) {
181 * callback("well done!");
182 * } else {
183 * errback("some programmers just want to watch the world burn");
184 * }
185 * }
186 *
187 * var promisified = callbacks.promisify(callbackAsLast, {
188 * callback: -1,
189 * errback: 0,
190 * });
191 *
192 * promisified(true).then(console.log, console.error);
193 * promisified(false).then(console.log, console.error);
194 *
195 * @param {Function} asyncFunction traditional function to be decorated
196 * @param {object} positions
197 * @param {number} [positions.callback] index at which asyncFunction expects to
198 * receive a success callback
199 * @param {number} [positions.errback] index at which asyncFunction expects to
200 * receive an error callback
201 * @returns {function} promisified function that accepts
202 *
203 * @deprecated
204 */
205 function promisify(asyncFunction, positions) {
206
207 return function() {
208 var thisArg = this;
209 return Promise.all(arguments).then(function(args) {
210 var p = Promise._defer();
211
212 var callbackPos, errbackPos;
213
214 if('callback' in positions) {
215 callbackPos = normalizePosition(args, positions.callback);
216 }
217
218 if('errback' in positions) {
219 errbackPos = normalizePosition(args, positions.errback);
220 }
221
222 if(errbackPos < callbackPos) {
223 insertCallback(args, errbackPos, p._handler.reject, p._handler);
224 insertCallback(args, callbackPos, p._handler.resolve, p._handler);
225 } else {
226 insertCallback(args, callbackPos, p._handler.resolve, p._handler);
227 insertCallback(args, errbackPos, p._handler.reject, p._handler);
228 }
229
230 asyncFunction.apply(thisArg, args);
231
232 return p;
233 });
234 };
235 }
236
237 function normalizePosition(args, pos) {
238 return pos < 0 ? (args.length + pos + 2) : pos;
239 }
240
241 function insertCallback(args, pos, callback, thisArg) {
242 if(pos != null) {
243 callback = alwaysUnary(callback, thisArg);
244 if(pos < 0) {
245 pos = args.length + pos + 2;
246 }
247 args.splice(pos, 0, callback);
248 }
249 }
250
251 function alwaysUnary(fn, thisArg) {
252 return function() {
253 if (arguments.length > 1) {
254 fn.call(thisArg, slice.call(arguments));
255 } else {
256 fn.apply(thisArg, arguments);
257 }
258 };
259 }
260});
261})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });