| /** @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); }); |