blob: 61d9453ff768e26c3a597be81eb6e2263d815a50 [file] [log] [blame]
/** @license MIT License (c) copyright 2013 original author or authors */
/**
* Collection of helpers for interfacing with node-style asynchronous functions
* using promises.
*
* @author Brian Cavalier
* @contributor Renato Zannon
*/
(function(define) {
define(function(require) {
var when = require('./when');
var _liftAll = require('./lib/liftAll');
var setTimer = require('./lib/env').setTimer;
var slice = Array.prototype.slice;
var _apply = require('./lib/apply')(when.Promise, dispatch);
return {
lift: lift,
liftAll: liftAll,
apply: apply,
call: call,
createCallback: createCallback,
bindCallback: bindCallback,
liftCallback: liftCallback
};
/**
* Takes a node-style async function and calls it immediately (with an optional
* array of arguments or promises for arguments). It returns a promise whose
* resolution depends on whether the async functions calls its callback with the
* conventional error argument or not.
*
* With this it becomes possible to leverage existing APIs while still reaping
* the benefits of promises.
*
* @example
* function onlySmallNumbers(n, callback) {
* if(n < 10) {
* callback(null, n + 10);
* } else {
* callback(new Error("Calculation failed"));
* }
* }
*
* var nodefn = require("when/node/function");
*
* // Logs '15'
* nodefn.apply(onlySmallNumbers, [5]).then(console.log, console.error);
*
* // Logs 'Calculation failed'
* nodefn.apply(onlySmallNumbers, [15]).then(console.log, console.error);
*
* @param {function} f node-style function that will be called
* @param {Array} [args] array of arguments to func
* @returns {Promise} promise for the value func passes to its callback
*/
function apply(f, args) {
return _apply(f, this, args || []);
}
function dispatch(f, thisArg, args, h) {
var cb = createCallback(h);
try {
switch(args.length) {
case 2: f.call(thisArg, args[0], args[1], cb); break;
case 1: f.call(thisArg, args[0], cb); break;
case 0: f.call(thisArg, cb); break;
default:
args.push(cb);
f.apply(thisArg, args);
}
} catch(e) {
h.reject(e);
}
}
/**
* Has the same behavior that {@link apply} has, with the difference that the
* arguments to the function are provided individually, while {@link apply} accepts
* a single array.
*
* @example
* function sumSmallNumbers(x, y, callback) {
* var result = x + y;
* if(result < 10) {
* callback(null, result);
* } else {
* callback(new Error("Calculation failed"));
* }
* }
*
* // Logs '5'
* nodefn.call(sumSmallNumbers, 2, 3).then(console.log, console.error);
*
* // Logs 'Calculation failed'
* nodefn.call(sumSmallNumbers, 5, 10).then(console.log, console.error);
*
* @param {function} f node-style function that will be called
* @param {...*} [args] arguments that will be forwarded to the function
* @returns {Promise} promise for the value func passes to its callback
*/
function call(f /*, args... */) {
return _apply(f, this, slice.call(arguments, 1));
}
/**
* Takes a node-style function and returns new function that wraps the
* original and, instead of taking a callback, returns a promise. Also, it
* knows how to handle promises given as arguments, waiting for their
* resolution before executing.
*
* Upon execution, the orginal function is executed as well. If it passes
* a truthy value as the first argument to the callback, it will be
* interpreted as an error condition, and the promise will be rejected
* with it. Otherwise, the call is considered a resolution, and the promise
* is resolved with the callback's second argument.
*
* @example
* var fs = require("fs"), nodefn = require("when/node/function");
*
* var promiseRead = nodefn.lift(fs.readFile);
*
* // The promise is resolved with the contents of the file if everything
* // goes ok
* promiseRead('exists.txt').then(console.log, console.error);
*
* // And will be rejected if something doesn't work out
* // (e.g. the files does not exist)
* promiseRead('doesnt_exist.txt').then(console.log, console.error);
*
*
* @param {Function} f node-style function to be lifted
* @param {...*} [args] arguments to be prepended for the new function @deprecated
* @returns {Function} a promise-returning function
*/
function lift(f /*, args... */) {
var args1 = arguments.length > 1 ? slice.call(arguments, 1) : [];
return function() {
// TODO: Simplify once partialing has been removed
var l = args1.length;
var al = arguments.length;
var args = new Array(al + l);
var i;
for(i=0; i<l; ++i) {
args[i] = args1[i];
}
for(i=0; i<al; ++i) {
args[i+l] = arguments[i];
}
return _apply(f, this, args);
};
}
/**
* 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);
}
/**
* Takes an object that responds to the resolver interface, and returns
* a function that will resolve or reject it depending on how it is called.
*
* @example
* function callbackTakingFunction(callback) {
* if(somethingWrongHappened) {
* callback(error);
* } else {
* callback(null, interestingValue);
* }
* }
*
* var when = require('when'), nodefn = require('when/node/function');
*
* var deferred = when.defer();
* callbackTakingFunction(nodefn.createCallback(deferred.resolver));
*
* deferred.promise.then(function(interestingValue) {
* // Use interestingValue
* });
*
* @param {Resolver} resolver that will be 'attached' to the callback
* @returns {Function} a node-style callback function
*/
function createCallback(resolver) {
return function(err, value) {
if(err) {
resolver.reject(err);
} else if(arguments.length > 2) {
resolver.resolve(slice.call(arguments, 1));
} else {
resolver.resolve(value);
}
};
}
/**
* Attaches a node-style callback to a promise, ensuring the callback is
* called for either fulfillment or rejection. Returns a promise with the same
* state as the passed-in promise.
*
* @example
* var deferred = when.defer();
*
* function callback(err, value) {
* // Handle err or use value
* }
*
* bindCallback(deferred.promise, callback);
*
* deferred.resolve('interesting value');
*
* @param {Promise} promise The promise to be attached to.
* @param {Function} callback The node-style callback to attach.
* @returns {Promise} A promise with the same state as the passed-in promise.
*/
function bindCallback(promise, callback) {
promise = when(promise);
if (callback) {
promise.then(success, wrapped);
}
return promise;
function success(value) {
wrapped(null, value);
}
function wrapped(err, value) {
setTimer(function () {
callback(err, value);
}, 0);
}
}
/**
* Takes a node-style callback and returns new function that accepts a
* promise, calling the original callback when the promise is either
* fulfilled or rejected with the appropriate arguments.
*
* @example
* var deferred = when.defer();
*
* function callback(err, value) {
* // Handle err or use value
* }
*
* var wrapped = liftCallback(callback);
*
* // `wrapped` can now be passed around at will
* wrapped(deferred.promise);
*
* deferred.resolve('interesting value');
*
* @param {Function} callback The node-style callback to wrap.
* @returns {Function} The lifted, promise-accepting function.
*/
function liftCallback(callback) {
return function(promise) {
return bindCallback(promise, callback);
};
}
});
})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); });