"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
*/
require('polyfill/polyfill-base.js');
require('polyfill/lib/promise.js'); // need promises
var NAME = '[promise-ext]: ',
FUNCTION_EXPECTED = ' expects an array of function-references', // include leading space!
later = require('utils').later,
PROMISE_CHAIN = 'Promise.chain';
(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: .finally() basicly means you want to execute code after the chain, regardless
* whether it's resolved or rejected.
*
* **Note:** .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 .thenFulfill().
*
* @method finally
* @param finallyback {Function} the callbackfunctio to be invoked.
* @return {Promise}
*/
PromisePrototype.finally = function (finallyback) {
console.log(NAME, 'finally');
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)
* .thenFulfill()
* .then(handleFulfilled, handleRejected) // handleFulfilled always gets invoked
* @method thenFulfill
* @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.thenFulfill = function (callback) {
console.log(NAME, 'thenFulfill');
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 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.finishAll = function (items) {
console.log(NAME, 'finishAll');
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) {
console.log(NAME, 'finishAll is fulfilled');
fulfill({
fulfilled: fulfilledresults,
rejected: rejectedresults
});
}
};
}
if (length < 1) {
console.warn(NAME, 'finishAll fulfilles immediately: no items');
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.chainFns(a).then(
* function(r) {
* console.log('chain resolved with '+r);
* },
* function(err) {
* console.log('chain-error '+err);
* }
* );
*
* @method 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.chainFns = function (funcs, finishAll) {
console.log(NAME, 'chainFns');
var handleFn, length, handlePromiseChain, promiseErr,
i = 0;
// Array.isArray assumes ES5
Array.isArray(funcs) || (funcs=[funcs]);
length = funcs.length;
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 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.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 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.manage = function (callbackFn, stayActive) {
console.log(NAME, 'manage');
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) {
console.log(NAME, 'manage.fulfill');
resolved = true;
if (stayActivePromise) {
stayActivePromise.then(function() {
finished = true;
fulfillHandler(value);
});
}
else {
finished = true;
fulfillHandler(value);
}
}
};
promise.reject = function (reason) {
if (!resolved) {
console.log(NAME, 'manage.reject '+((typeof reason==='string') ? reason : reason && (reason.message || reason.description)));
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) {
later(fulfill, time);
});
};
promise.callback = function () {
if (!finished && callbackFn) {
console.log(NAME, 'manage.callback is invoked');
callbackFn.apply(undefined, arguments);
}
};
promise.setCallback = function (newCallbackFn) {
callbackFn = newCallbackFn;
};
stayActive && promise.stayActive(stayActive);
promise.then(
function() {
isFulfilled = true;
},
function() {
isRejected = true;
}
);
return promise;
};