You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
8.2 KiB
282 lines
8.2 KiB
/** @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); }); |
|
|
|
|
|
|
|
|