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 | 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); });
|