UNPKG

promised-redmine

Version:

Redmine Rest API Client for node.js with Promises/A+ compliance

450 lines (413 loc) 14.6 kB
/** * attempt of a simple defer/promise library for mobile development * @author Jonathan Gotti < jgotti at jgotti dot net> * @since 2012-10 * @version 0.7.5 */ (function(undef){ "use strict"; var nextTick , isFunc = function(f){ return ( typeof f === 'function' ); } , isArray = function(a){ return Array.isArray ? Array.isArray(a) : (a instanceof Array); } , isObjOrFunc = function(o){ return !!(o && (typeof o).match(/function|object/)); } , isNotVal = function(v){ return (v === false || v === undef || v === null); } , slice = function(a, offset){ return [].slice.call(a, offset); } , undefStr = 'undefined' , tErr = typeof TypeError === undefStr ? Error : TypeError ; if ( (typeof process !== undefStr) && process.nextTick ) { nextTick = process.nextTick; } else if ( typeof MessageChannel !== undefStr ) { var ntickChannel = new MessageChannel(), queue = []; ntickChannel.port1.onmessage = function(){ queue.length && (queue.shift())(); }; nextTick = function(cb){ queue.push(cb); ntickChannel.port2.postMessage(0); }; } else { nextTick = function(cb){ setTimeout(cb, 0); }; } function rethrow(e){ nextTick(function(){ throw e;}); } /** * a function called on fulfilled promise resolution * @typedef {function} fulfilled * @param {*} value promise resolved value * @returns {*} next promise resolution value */ /** * a function called on failed promise resolution * @typedef {function} failed * @param {*} reason promise rejection reason * @returns {*} next promise resolution value or rethrow the reason */ //-- defining unenclosed promise methods --// /** * same as then without failed callback * @param {fulfilled} fulfilled callback * @returns {promise} a new promise */ function promise_success(fulfilled){ return this.then(fulfilled, undef); } /** * same as then with only a failed callback * @param {failed} failed callback * @returns {promise} a new promise */ function promise_error(failed){ return this.then(undef, failed); } /** * same as then but fulfilled callback will receive multiple parameters when promise is fulfilled with an Array * @param {fulfilled} fulfilled callback * @param {failed} failed callback * @returns {promise} a new promise */ function promise_apply(fulfilled, failed){ return this.then( function(a){ return isFunc(fulfilled) ? fulfilled.apply(null, isArray(a) ? a : [a]) : (defer.onlyFuncs ? a : fulfilled); } , failed || undef ); } /** * cleanup method which will be always executed regardless fulfillment or rejection * @param {function} cb a callback called regardless of the fulfillment or rejection of the promise which will be called * when the promise is not pending anymore * @returns {promise} the same promise untouched */ function promise_ensure(cb){ function _cb(){ cb(); } this.then(_cb, _cb); return this; } /** * take a single callback which wait for an error as first parameter. other resolution values are passed as with the apply/spread method * @param {function} cb a callback called regardless of the fulfillment or rejection of the promise which will be called * when the promise is not pending anymore with error as first parameter if any as in node style * callback. Rest of parameters will be applied as with the apply method. * @returns {promise} a new promise */ function promise_nodify(cb){ return this.then( function(a){ return isFunc(cb) ? cb.apply(null, isArray(a) ? a.splice(0,0,undefined) && a : [undefined,a]) : (defer.onlyFuncs ? a : cb); } , function(e){ return cb(e); } ); } /** * * @param {function} [failed] without parameter will only rethrow promise rejection reason outside of the promise library on next tick * if passed a failed method then will call failed on rejection and throw the error again if failed didn't * @returns {promise} a new promise */ function promise_rethrow(failed){ return this.then( undef , failed ? function(e){ failed(e); throw e; } : rethrow ); } /** * return a defer object * @param {boolean} [alwaysAsync] if set force the async resolution for this promise independantly of the D.alwaysAsync option * @returns {deferred} defered object with property 'promise' and methods reject,fulfill,resolve (fulfill being an alias for resolve) */ var defer = function (alwaysAsync){ var alwaysAsyncFn = (undef !== alwaysAsync ? alwaysAsync : defer.alwaysAsync) ? nextTick : function(fn){fn();} , status = 0 // -1 failed | 1 fulfilled , pendings = [] , value /** * @typedef promise */ , _promise = { /** * @param {fulfilled|function} fulfilled callback * @param {failed|function} failed callback * @returns {promise} a new promise */ then: function(fulfilled, failed){ var d = defer(); pendings.push([ function(value){ try{ if( isNotVal(fulfilled)){ d.resolve(value); } else { d.resolve(isFunc(fulfilled) ? fulfilled(value) : (defer.onlyFuncs ? value : fulfilled)); } }catch(e){ d.reject(e); } } , function(err){ if ( isNotVal(failed) || ((!isFunc(failed)) && defer.onlyFuncs) ) { d.reject(err); } if ( failed ) { try{ d.resolve(isFunc(failed) ? failed(err) : failed); }catch(e){ d.reject(e);} } } ]); status !== 0 && alwaysAsyncFn(execCallbacks); return d.promise; } , success: promise_success , error: promise_error , otherwise: promise_error , apply: promise_apply , spread: promise_apply , ensure: promise_ensure , nodify: promise_nodify , rethrow: promise_rethrow , isPending: function(){ return status === 0; } , getStatus: function(){ return status; } } ; _promise.toSource = _promise.toString = _promise.valueOf = function(){return value === undef ? this : value; }; function execCallbacks(){ /*jshint bitwise:false*/ if ( status === 0 ) { return; } var cbs = pendings, i = 0, l = cbs.length, cbIndex = ~status ? 0 : 1, cb; pendings = []; for( ; i < l; i++ ){ (cb = cbs[i][cbIndex]) && cb(value); } } /** * fulfill deferred with given value * @param {*} val * @returns {deferred} this for method chaining */ function _resolve(val){ var done = false; function once(f){ return function(x){ if (done) { return undefined; } else { done = true; return f(x); } }; } if ( status ) { return this; } try { var then = isObjOrFunc(val) && val.then; if ( isFunc(then) ) { // managing a promise if( val === _promise ){ throw new tErr("Promise can't resolve itself"); } then.call(val, once(_resolve), once(_reject)); return this; } } catch (e) { once(_reject)(e); return this; } alwaysAsyncFn(function(){ value = val; status = 1; execCallbacks(); }); return this; } /** * reject deferred with given reason * @param {*} Err * @returns {deferred} this for method chaining */ function _reject(Err){ status || alwaysAsyncFn(function(){ try{ throw(Err); }catch(e){ value = e; } status = -1; execCallbacks(); }); return this; } return /**@type deferred */ { promise:_promise ,resolve:_resolve ,fulfill:_resolve // alias ,reject:_reject }; }; defer.deferred = defer.defer = defer; defer.nextTick = nextTick; defer.alwaysAsync = true; // setting this will change default behaviour. use it only if necessary as asynchronicity will force some delay between your promise resolutions and is not always what you want. /** * setting onlyFuncs to false will break promises/A+ conformity by allowing you to pass non undefined/null values instead of callbacks * instead of just ignoring any non function parameters to then,success,error... it will accept non null|undefined values. * this will allow you shortcuts like promise.then('val','handled error'') * to be equivalent of promise.then(function(){ return 'val';},function(){ return 'handled error'}) */ defer.onlyFuncs = true; /** * return a fulfilled promise of given value (always async resolution) * @param {*} value * @returns {promise} */ defer.resolve = defer.resolved = defer.fulfilled = function(value){ return defer(true).resolve(value).promise; }; /** * return a rejected promise with given reason of rejection (always async rejection) * @param {*} reason * @returns {promise} */ defer.reject = defer.rejected = function(reason){ return defer(true).reject(reason).promise; }; /** * return a promise with no resolution value which will be resolved in time ms (using setTimeout) * @param {int} [time] in ms default to 0 * @returns {promise} */ defer.wait = function(time){ var d = defer(); setTimeout(d.resolve, time || 0); return d.promise; }; /** * return a promise for the return value of function call which will be fulfilled in delay ms or rejected if given fn throw an error * @param {*} fn to execute or value to return after given delay * @param {int} [delay] in ms default to 0 * @returns {promise} */ defer.delay = function(fn, delay){ var d = defer(); setTimeout(function(){ try{ d.resolve(isFunc(fn) ? fn.apply(null) : fn); }catch(e){ d.reject(e); } }, delay || 0); return d.promise; }; /** * if given value is not a promise return a fulfilled promise resolved to given value * @param {*} promise a value or a promise * @returns {promise} */ defer.promisify = function(promise){ if ( promise && isFunc(promise.then) ) { return promise;} return defer.resolved(promise); }; function multiPromiseResolver(callerArguments, returnPromises){ var promises = slice(callerArguments); if ( promises.length === 1 && isArray(promises[0]) ) { if(! promises[0].length ){ return defer.fulfilled([]); } promises = promises[0]; } var args = [] , d = defer() , c = promises.length ; if ( !c ) { d.resolve(args); } else { var resolver = function(i){ promises[i] = defer.promisify(promises[i]); promises[i].then( function(v){ args[i] = returnPromises ? promises[i] : v; (--c) || d.resolve(args); } , function(e){ if( ! returnPromises ){ d.reject(e); } else { args[i] = promises[i]; (--c) || d.resolve(args); } } ); }; for( var i = 0, l = c; i < l; i++ ){ resolver(i); } } return d.promise; } function sequenceZenifier(promise, zenValue){ return promise.then(isFunc(zenValue) ? zenValue : function(){return zenValue;}); } function sequencePromiseResolver(callerArguments){ var funcs = slice(callerArguments); if ( funcs.length === 1 && isArray(funcs[0]) ) { funcs = funcs[0]; } var d = defer(), i=0, l=funcs.length, promise = defer.resolved(); for(; i<l; i++){ promise = sequenceZenifier(promise, funcs[i]); } d.resolve(promise); return d.promise; } /** * return a promise for all given promises / values. * the returned promises will be fulfilled with a list of resolved value. * if any given promise is rejected then on the first rejection the returned promised will be rejected with the same reason * @param {array|...*} [promise] can be a single array of promise/values as first parameter or a list of direct parameters promise/value * @returns {promise} of a list of given promise resolution value */ defer.all = function(){ return multiPromiseResolver(arguments,false); }; /** * return an always fulfilled promise of array<promise> list of promises/values regardless they resolve fulfilled or rejected * @param {array|...*} [promise] can be a single array of promise/values as first parameter or a list of direct parameters promise/value * (non promise values will be promisified) * @returns {promise} of the list of given promises */ defer.resolveAll = function(){ return multiPromiseResolver(arguments,true); }; /** * execute given function in sequence passing their returned values to the next one in sequence. * You can pass values or promise instead of functions they will be passed in the sequence as if a function returned them. * if any function throw an error or a rejected promise the final returned promise will be rejected with that reason. * @param {array|...*} [function] list of function to call in sequence receiving previous one as a parameter * (non function values will be treated as if returned by a function) * @returns {promise} of the list of given promises */ defer.sequence = function(){ return sequencePromiseResolver(arguments); }; /** * transform a typical nodejs async method awaiting a callback as last parameter, receiving error as first parameter to a function that * will return a promise instead. the returned promise will resolve with normal callback value minus the first error parameter on * fulfill and will be rejected with that error as reason in case of error. * @param {object} [subject] optional subject of the method to encapsulate * @param {function} fn the function to encapsulate if the normal callback should receive more than a single parameter (minus the error) * the promise will resolve with the list or parameters as fulfillment value. If only one parameter is sent to the * callback then it will be used as the resolution value. * @returns {Function} */ defer.nodeCapsule = function(subject, fn){ if ( !fn ) { fn = subject; subject = void(0); } return function(){ var d = defer(), args = slice(arguments); args.push(function(err, res){ err ? d.reject(err) : d.resolve(arguments.length > 2 ? slice(arguments, 1) : res); }); try{ fn.apply(subject, args); }catch(e){ d.reject(e); } return d.promise; }; }; /*global define*/ if ( typeof define === 'function' && define.amd ) { define('D.js', [], function(){ return defer; }); } else if ( typeof module !== undefStr && module.exports ) { module.exports = defer; } else if ( typeof window !== undefStr ) { var oldD = window.D; /** * restore global D variable to its previous value and return D to the user * @returns {Function} */ defer.noConflict = function(){ window.D = oldD; return defer; }; window.D = defer; } })();