UNPKG

itsa-jsext

Version:

Extensions to native javascript-objects, all within the itsa_ namespace

368 lines (342 loc) 13.8 kB
/* global Promise:true */ "use strict"; /** * Provides additional Promise-methods. These are extra methods which are not part of the PromiseA+ specification, * But are all Promise/A+ compatable. * * <i>Copyright (c) 2014 ITSA - https://github.com/itsa</i> * New BSD License - http://choosealicense.com/licenses/bsd-3-clause/ * * * @module js-ext * @submodule lib/promise.s * @class Promise */ var FUNCTION_EXPECTED = " expects an array of function-references", // include leading space FUNCTION = "function", PROMISE_CHAIN = "Promise.itsa_chainFns"; if (Promise) { (function(PromisePrototype) { /** * Promise which can be put at the very end of a chain, even after .catch(). * Will invoke the callback function regardless whether the chain resolves or rejects. * * The argument of the callback will be either its fulfilled or rejected argument, but * it is wisely not to handle it. The results should have been handled in an earlier step * of the chain: .itsa_finally() basicly means you want to execute code after the chain, regardless * whether it's resolved or rejected. * * **Note:** .itsa_finally() <u>does not return a Promise</u>: it should be used as the very last step of a Promisechain. * If you need an intermediate method, you should take .itsa_fulfillThen(). * * @method itsa_finally * @param finallyback {Function} the callback-function to be invoked. * @return {Promise} */ PromisePrototype.itsa_finally = function (finallyback) { return this.then(finallyback, finallyback); }; /** * Will always return a fulfilled Promise. * * Typical usage will be by making it part of a Promisechain: it makes the chain go * into its fulfilled phase. * * @example * * promise1 * .then(promise2) * .itsa_fulfillThen() * .then(handleFulfilled, handleRejected) // handleFulfilled always gets invoked * @method itsa_fulfillThen * @param [response] {Object} parameter to pass through which overrules the original Promise-response. * @return {Promise} Resolved Promise. `response` will be passed trough as parameter when set. * When not set: in case the original Promise resolved, its parameter is passed through. * in case of a rejection, no parameter will be passed through. */ PromisePrototype.itsa_fulfillThen = function (callback) { return this.then( function(r) { return r; }, function(r) { return r; } ).then(callback); }; }(Promise.prototype)); /** * Returns a Promise that always fulfills. It is fulfilled when ALL items are resolved (either fulfilled * or rejected). This is useful for waiting for the resolution of multiple * promises, such as reading multiple files in Node.js or making multiple XHR * requests in the browser. Because -on the contrary of `Promise.all`- **finishAll** waits until * all single Promises are resolved, you can handle all promises, even if some gets rejected. * * @method itsa_finishAll * @param items {Any[]} an array of any kind of items, promises or not. If a value is not a promise, * its transformed into a resolved promise. * @return {Promise} A promise for an array of all the fulfillment items: * <ul> * <li>Fulfilled: o {Object} * <ul> * <li>fulfilled {Array} all fulfilled responses, any item that was rejected will have a value of `undefined`</li> * <li>rejected {Array} all rejected responses, any item that was fulfilled will have a value of `undefined`</li> * </ul> * </li> * <li>Rejected: this promise **never** rejects</li> * </ul> * @static */ Promise.itsa_finishAll = function (items) { return new Promise(function (fulfill) { // Array.isArray assumes ES5 Array.isArray(items) || (items=[items]); var remaining = items.length, length = items.length, fulfilledresults = [], rejectedresults = [], i; function oneDone(index, fulfilled) { return function (value) { fulfilled ? (fulfilledresults[index]=value) : (rejectedresults[index]=value); remaining--; if (!remaining) { fulfill({ fulfilled: fulfilledresults, rejected: rejectedresults }); } }; } if (length < 1) { return fulfill({ fulfilled: fulfilledresults, rejected: rejectedresults }); } fulfilledresults.length = length; rejectedresults.length = length; for (i=0; i < length; i++) { Promise.resolve(items[i]).then(oneDone(i, true), oneDone(i, false)); } }); }; /** * Returns a Promise which chains the function-calls. Like an automated Promise-chain. * Invokes the functionreferences in a chain. You MUST supply function-references, it doesn't * matter wheter these functions return a Promise or not. Any returnvalues are passed through to * the next function. * * **Cautious:** you need to pass function-references, not invoke them! * chainFns will invoke them when the time is ready. Regarding to this, there is a difference with * using Promise.all() where you should pass invoked Promises. * * If one of the functions returns a Promise, the chain * will wait its execution for this function to be resolved. * * If you need specific context or arguments: use Function.bind for these items. * If one of the items returns a rejected Promise, by default: the whole chain rejects * and following functions in the chain will not be invoked. When `finishAll` is set `true` * the chain will always continue even with rejected Promises. * * Returning functionvalues are passed through the chain adding them as an extra argument * to the next function in the chain (argument is added on the right) * * @example * var a = [], p1, p2, p3; * p1 = function(a) { * return new Promise(function(resolve, reject) { * I.later(function() { * console.log('resolving promise p1: '+a); * resolve(a); * }, 1000); * }); * }; * p2 = function(b, r) { * var value = b+r; * console.log('returning p2: '+value); * return value; * }; * p3 = function(c, r) { * return new Promise(function(resolve, reject) { * I.later(function() { * var value = b+r; * console.log('resolving promise p3: '+value); * resolve(value); * }, 1000); * }); * }; * a.push(p1.bind(undefined, 100)); * a.push(p2.bind(undefined, 200)); * a.push(p3.bind(undefined, 300)); * Promise.itsa_chainFns(a).then( * function(r) { * console.log('chain resolved with '+r); * }, * function(err) { * console.log('chain-error '+err); * } * ); * * @method itsa_chainFns * @param funcs {function[]} an array of function-references * @param [finishAll=false] {boolean} to force the chain to continue, even if one of the functions * returns a rejected Promise * @return {Promise} * on success: * o {Object} returnvalue of the laste item in the Promisechain * on failure an Error object * reason {Error} * @static */ Promise.itsa_chainFns = function (funcs, finishAll) { var handleFn, length, handlePromiseChain, promiseErr, i = 0; // Array.isArray assumes ES5 Array.isArray(funcs) || (funcs=[funcs]); length = funcs.length; if (!length) { return Promise.resolve(); } handleFn = function() { var nextFn = funcs[i], promise; if (typeof nextFn !== FUNCTION) { return Promise.reject(new TypeError(PROMISE_CHAIN+FUNCTION_EXPECTED)); } promise = Promise.resolve(nextFn.apply(null, arguments)); // by using "promise.catch(function(){})" we return a resolved Promise return finishAll ? promise.catch(function(err){ promiseErr = err; return err; }) : promise; }; handlePromiseChain = function() { // will loop until rejected, which is at destruction of the class return handleFn.apply(null, arguments).then((++i<length) ? handlePromiseChain : undefined); }; return handlePromiseChain().then(function(response) { // if (promiseErr) { // throw new Error(promiseErr); // } return promiseErr || response; }); }; /** * Returns a Promise with 5 additional methods: * * promise.fulfill * promise.reject * promise.callback * promise.setCallback * promise.pending * promise.stayActive --> force the promise not to resolve in the specified time * * With Promise.manage, you get a Promise which is managable from outside, not inside as Promise A+ work. * You can invoke promise.**callback**() which will invoke the original passed-in callbackFn - if any. * promise.**fulfill**() and promise.**reject**() are meant to resolve the promise from outside, just like deferred can do. * * If `stayActive` is defined, the promise will only be resolved after this specified time (ms). When `fulfill` or `reject` is * called, it will be applied after this specified time. * * @example * var promise = Promise.itsa_manage( * function(msg) { * alert(msg); * } * ); * * promise.then( * function() { * // promise is fulfilled, no further actions can be taken * } * ); * * setTimeout(function() { * promise.callback('hey, I\'m still busy'); * }, 1000); * * setTimeout(function() { * promise.fulfill(); * }, 2000); * * @method itsa_manage * @param [callbackFn] {Function} invoked everytime promiseinstance.callback() is called. * You may as weel (re)set this method atny time lare by using promise.setCallback() * @param [stayActive=false] {Boolean} specified time to wait before the promise really gets resolved * @return {Promise} with three handles: fulfill, reject and callback. * @static */ Promise.itsa_manage = function (callbackFn, stayActive) { var fulfillHandler, rejectHandler, promise, finished, stayActivePromise, resolved, isFulfilled, isRejected; promise = new Promise(function (fulfill, reject) { fulfillHandler = fulfill; rejectHandler = reject; }); promise.fulfill = function (value) { if (!resolved) { resolved = true; if (stayActivePromise) { stayActivePromise.then(function() { finished = true; fulfillHandler(value); }); } else { finished = true; fulfillHandler(value); } } }; promise.reject = function (reason) { if (!resolved) { resolved = true; if (stayActivePromise) { stayActivePromise.then(function() { finished = true; rejectHandler(reason); }); } else { finished = true; rejectHandler(reason); } } }; promise.pending = function () { return !finished; }; promise.isFulfilled = function () { return !!isFulfilled; }; promise.isRejected = function () { return !!isRejected; }; promise.stayActive = function (time) { stayActivePromise = new Promise(function (fulfill) { setTimeout(fulfill, time); }); }; promise.callback = function () { if (!finished && callbackFn) { callbackFn.apply(undefined, arguments); } }; promise.setCallback = function (newCallbackFn) { callbackFn = newCallbackFn; }; stayActive && promise.stayActive(stayActive); promise.then( function() { isFulfilled = true; }, function() { isRejected = true; } ); return promise; }; }