diff options
Diffstat (limited to 'node_modules/when/callbacks.js')
-rw-r--r-- | node_modules/when/callbacks.js | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/node_modules/when/callbacks.js b/node_modules/when/callbacks.js new file mode 100644 index 000000000..00e414b51 --- /dev/null +++ b/node_modules/when/callbacks.js @@ -0,0 +1,262 @@ +/** @license MIT License (c) copyright 2013-2014 original author or authors */ + +/** + * Collection of helper functions for interacting with 'traditional', + * callback-taking functions using a promise interface. + * + * @author Renato Zannon + * @contributor Brian Cavalier + */ + +(function(define) { +define(function(require) { + + var when = require('./when'); + var Promise = when.Promise; + var _liftAll = require('./lib/liftAll'); + var slice = Array.prototype.slice; + + var makeApply = require('./lib/apply'); + var _apply = makeApply(Promise, dispatch); + + return { + lift: lift, + liftAll: liftAll, + apply: apply, + call: call, + promisify: promisify + }; + + /** + * Takes a `traditional` callback-taking function and returns a promise for its + * result, accepting an optional array of arguments (that might be values or + * promises). It assumes that the function takes its callback and errback as + * the last two arguments. The resolution of the promise depends on whether the + * function will call its callback or its errback. + * + * @example + * var domIsLoaded = callbacks.apply($); + * domIsLoaded.then(function() { + * doMyDomStuff(); + * }); + * + * @example + * function existingAjaxyFunction(url, callback, errback) { + * // Complex logic you'd rather not change + * } + * + * var promise = callbacks.apply(existingAjaxyFunction, ["/movies.json"]); + * + * promise.then(function(movies) { + * // Work with movies + * }, function(reason) { + * // Handle error + * }); + * + * @param {function} asyncFunction function to be called + * @param {Array} [extraAsyncArgs] array of arguments to asyncFunction + * @returns {Promise} promise for the callback value of asyncFunction + */ + function apply(asyncFunction, extraAsyncArgs) { + return _apply(asyncFunction, this, extraAsyncArgs || []); + } + + /** + * Apply helper that allows specifying thisArg + * @private + */ + function dispatch(f, thisArg, args, h) { + args.push(alwaysUnary(h.resolve, h), alwaysUnary(h.reject, h)); + tryCatchResolve(f, thisArg, args, h); + } + + function tryCatchResolve(f, thisArg, args, resolver) { + try { + f.apply(thisArg, args); + } catch(e) { + resolver.reject(e); + } + } + + /** + * Works as `callbacks.apply` does, with the difference that the arguments to + * the function are passed individually, instead of as an array. + * + * @example + * function sumInFiveSeconds(a, b, callback) { + * setTimeout(function() { + * callback(a + b); + * }, 5000); + * } + * + * var sumPromise = callbacks.call(sumInFiveSeconds, 5, 10); + * + * // Logs '15' 5 seconds later + * sumPromise.then(console.log); + * + * @param {function} asyncFunction function to be called + * @param {...*} args arguments that will be forwarded to the function + * @returns {Promise} promise for the callback value of asyncFunction + */ + function call(asyncFunction/*, arg1, arg2...*/) { + return _apply(asyncFunction, this, slice.call(arguments, 1)); + } + + /** + * Takes a 'traditional' callback/errback-taking function and returns a function + * that returns a promise instead. The resolution/rejection of the promise + * depends on whether the original function will call its callback or its + * errback. + * + * If additional arguments are passed to the `lift` call, they will be prepended + * on the calls to the original function, much like `Function.prototype.bind`. + * + * The resulting function is also "promise-aware", in the sense that, if given + * promises as arguments, it will wait for their resolution before executing. + * + * @example + * function traditionalAjax(method, url, callback, errback) { + * var xhr = new XMLHttpRequest(); + * xhr.open(method, url); + * + * xhr.onload = callback; + * xhr.onerror = errback; + * + * xhr.send(); + * } + * + * var promiseAjax = callbacks.lift(traditionalAjax); + * promiseAjax("GET", "/movies.json").then(console.log, console.error); + * + * var promiseAjaxGet = callbacks.lift(traditionalAjax, "GET"); + * promiseAjaxGet("/movies.json").then(console.log, console.error); + * + * @param {Function} f traditional async function to be decorated + * @param {...*} [args] arguments to be prepended for the new function @deprecated + * @returns {Function} a promise-returning function + */ + function lift(f/*, args...*/) { + var args = arguments.length > 1 ? slice.call(arguments, 1) : []; + return function() { + return _apply(f, this, args.concat(slice.call(arguments))); + }; + } + + /** + * 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); + } + + /** + * `promisify` is a version of `lift` that allows fine-grained control over the + * arguments that passed to the underlying function. It is intended to handle + * functions that don't follow the common callback and errback positions. + * + * The control is done by passing an object whose 'callback' and/or 'errback' + * keys, whose values are the corresponding 0-based indexes of the arguments on + * the function. Negative values are interpreted as being relative to the end + * of the arguments array. + * + * If arguments are given on the call to the 'promisified' function, they are + * intermingled with the callback and errback. If a promise is given among them, + * the execution of the function will only occur after its resolution. + * + * @example + * var delay = callbacks.promisify(setTimeout, { + * callback: 0 + * }); + * + * delay(100).then(function() { + * console.log("This happens 100ms afterwards"); + * }); + * + * @example + * function callbackAsLast(errback, followsStandards, callback) { + * if(followsStandards) { + * callback("well done!"); + * } else { + * errback("some programmers just want to watch the world burn"); + * } + * } + * + * var promisified = callbacks.promisify(callbackAsLast, { + * callback: -1, + * errback: 0, + * }); + * + * promisified(true).then(console.log, console.error); + * promisified(false).then(console.log, console.error); + * + * @param {Function} asyncFunction traditional function to be decorated + * @param {object} positions + * @param {number} [positions.callback] index at which asyncFunction expects to + * receive a success callback + * @param {number} [positions.errback] index at which asyncFunction expects to + * receive an error callback + * @returns {function} promisified function that accepts + * + * @deprecated + */ + function promisify(asyncFunction, positions) { + + return function() { + var thisArg = this; + return Promise.all(arguments).then(function(args) { + var p = Promise._defer(); + + var callbackPos, errbackPos; + + if(typeof positions.callback === 'number') { + callbackPos = normalizePosition(args, positions.callback); + } + + if(typeof positions.errback === 'number') { + errbackPos = normalizePosition(args, positions.errback); + } + + if(errbackPos < callbackPos) { + insertCallback(args, errbackPos, p._handler.reject, p._handler); + insertCallback(args, callbackPos, p._handler.resolve, p._handler); + } else { + insertCallback(args, callbackPos, p._handler.resolve, p._handler); + insertCallback(args, errbackPos, p._handler.reject, p._handler); + } + + asyncFunction.apply(thisArg, args); + + return p; + }); + }; + } + + function normalizePosition(args, pos) { + return pos < 0 ? (args.length + pos + 2) : pos; + } + + function insertCallback(args, pos, callback, thisArg) { + if(typeof pos === 'number') { + args.splice(pos, 0, alwaysUnary(callback, thisArg)); + } + } + + function alwaysUnary(fn, thisArg) { + return function() { + if (arguments.length > 1) { + fn.call(thisArg, slice.call(arguments)); + } else { + fn.apply(thisArg, arguments); + } + }; + } +}); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); |