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