| 'use strict'; | |
| /** | |
| * Promise | |
| * | |
| * Inspired by https://gist.github.com/RubaXa/8501359 from RubaXa <trash@rubaxa.org> | |
| * | |
| * @param {Function} handler Called as handler(resolve: Function, reject: Function) | |
| * @param {Promise} [parent] Parent promise for propagation of cancel and timeout | |
| */ | |
| function Promise(handler, parent) { | |
| var me = this; | |
| if (!(this instanceof Promise)) { | |
| throw new SyntaxError('Constructor must be called with the new operator'); | |
| } | |
| if (typeof handler !== 'function') { | |
| throw new SyntaxError('Function parameter handler(resolve, reject) missing'); | |
| } | |
| var _onSuccess = []; | |
| var _onFail = []; | |
| // status | |
| this.resolved = false; | |
| this.rejected = false; | |
| this.pending = true; | |
| /** | |
| * Process onSuccess and onFail callbacks: add them to the queue. | |
| * Once the promise is resolve, the function _promise is replace. | |
| * @param {Function} onSuccess | |
| * @param {Function} onFail | |
| * @private | |
| */ | |
| var _process = function (onSuccess, onFail) { | |
| _onSuccess.push(onSuccess); | |
| _onFail.push(onFail); | |
| }; | |
| /** | |
| * Add an onSuccess callback and optionally an onFail callback to the Promise | |
| * @param {Function} onSuccess | |
| * @param {Function} [onFail] | |
| * @returns {Promise} promise | |
| */ | |
| this.then = function (onSuccess, onFail) { | |
| return new Promise(function (resolve, reject) { | |
| var s = onSuccess ? _then(onSuccess, resolve, reject) : resolve; | |
| var f = onFail ? _then(onFail, resolve, reject) : reject; | |
| _process(s, f); | |
| }, me); | |
| }; | |
| /** | |
| * Resolve the promise | |
| * @param {*} result | |
| * @type {Function} | |
| */ | |
| var _resolve = function (result) { | |
| // update status | |
| me.resolved = true; | |
| me.rejected = false; | |
| me.pending = false; | |
| _onSuccess.forEach(function (fn) { | |
| fn(result); | |
| }); | |
| _process = function (onSuccess, onFail) { | |
| onSuccess(result); | |
| }; | |
| _resolve = _reject = function () { }; | |
| return me; | |
| }; | |
| /** | |
| * Reject the promise | |
| * @param {Error} error | |
| * @type {Function} | |
| */ | |
| var _reject = function (error) { | |
| // update status | |
| me.resolved = false; | |
| me.rejected = true; | |
| me.pending = false; | |
| _onFail.forEach(function (fn) { | |
| fn(error); | |
| }); | |
| _process = function (onSuccess, onFail) { | |
| onFail(error); | |
| }; | |
| _resolve = _reject = function () { } | |
| return me; | |
| }; | |
| /** | |
| * Cancel te promise. This will reject the promise with a CancellationError | |
| * @returns {Promise} self | |
| */ | |
| this.cancel = function () { | |
| if (parent) { | |
| parent.cancel(); | |
| } | |
| else { | |
| _reject(new CancellationError()); | |
| } | |
| return me; | |
| }; | |
| /** | |
| * Set a timeout for the promise. If the promise is not resolved within | |
| * the time, the promise will be cancelled and a TimeoutError is thrown. | |
| * If the promise is resolved in time, the timeout is removed. | |
| * @param {number} delay Delay in milliseconds | |
| * @returns {Promise} self | |
| */ | |
| this.timeout = function (delay) { | |
| if (parent) { | |
| parent.timeout(delay); | |
| } | |
| else { | |
| var timer = setTimeout(function () { | |
| _reject(new TimeoutError('Promise timed out after ' + delay + ' ms')); | |
| }, delay); | |
| me.always(function () { | |
| clearTimeout(timer); | |
| }); | |
| } | |
| return me; | |
| }; | |
| // attach handler passing the resolve and reject functions | |
| handler(function (result) { | |
| _resolve(result); | |
| }, function (error) { | |
| _reject(error); | |
| }); | |
| } | |
| /** | |
| * Execute given callback, then call resolve/reject based on the returned result | |
| * @param {Function} callback | |
| * @param {Function} resolve | |
| * @param {Function} reject | |
| * @returns {Function} | |
| * @private | |
| */ | |
| function _then(callback, resolve, reject) { | |
| return function (result) { | |
| try { | |
| var res = callback(result); | |
| if (res && typeof res.then === 'function' && typeof res['catch'] === 'function') { | |
| // method returned a promise | |
| res.then(resolve, reject); | |
| } | |
| else { | |
| resolve(res); | |
| } | |
| } | |
| catch (error) { | |
| reject(error); | |
| } | |
| } | |
| } | |
| /** | |
| * Add an onFail callback to the Promise | |
| * @param {Function} onFail | |
| * @returns {Promise} promise | |
| */ | |
| Promise.prototype['catch'] = function (onFail) { | |
| return this.then(null, onFail); | |
| }; | |
| // TODO: add support for Promise.catch(Error, callback) | |
| // TODO: add support for Promise.catch(Error, Error, callback) | |
| /** | |
| * Execute given callback when the promise either resolves or rejects. | |
| * @param {Function} fn | |
| * @returns {Promise} promise | |
| */ | |
| Promise.prototype.always = function (fn) { | |
| return this.then(fn, fn); | |
| }; | |
| /** | |
| * Create a promise which resolves when all provided promises are resolved, | |
| * and fails when any of the promises resolves. | |
| * @param {Promise[]} promises | |
| * @returns {Promise} promise | |
| */ | |
| Promise.all = function (promises){ | |
| return new Promise(function (resolve, reject) { | |
| var remaining = promises.length, | |
| results = []; | |
| if (remaining) { | |
| promises.forEach(function (p, i) { | |
| p.then(function (result) { | |
| results[i] = result; | |
| remaining--; | |
| if (remaining == 0) { | |
| resolve(results); | |
| } | |
| }, function (error) { | |
| remaining = 0; | |
| reject(error); | |
| }); | |
| }); | |
| } | |
| else { | |
| resolve(results); | |
| } | |
| }); | |
| }; | |
| /** | |
| * Create a promise resolver | |
| * @returns {{promise: Promise, resolve: Function, reject: Function}} resolver | |
| */ | |
| Promise.defer = function () { | |
| var resolver = {}; | |
| resolver.promise = new Promise(function (resolve, reject) { | |
| resolver.resolve = resolve; | |
| resolver.reject = reject; | |
| }); | |
| return resolver; | |
| }; | |
| /** | |
| * Create a cancellation error | |
| * @param {String} [message] | |
| * @extends Error | |
| */ | |
| function CancellationError(message) { | |
| this.message = message || 'promise cancelled'; | |
| this.stack = (new Error()).stack; | |
| } | |
| CancellationError.prototype = new Error(); | |
| CancellationError.prototype.constructor = Error; | |
| CancellationError.prototype.name = 'CancellationError'; | |
| Promise.CancellationError = CancellationError; | |
| /** | |
| * Create a timeout error | |
| * @param {String} [message] | |
| * @extends Error | |
| */ | |
| function TimeoutError(message) { | |
| this.message = message || 'timeout exceeded'; | |
| this.stack = (new Error()).stack; | |
| } | |
| TimeoutError.prototype = new Error(); | |
| TimeoutError.prototype.constructor = Error; | |
| TimeoutError.prototype.name = 'TimeoutError'; | |
| Promise.TimeoutError = TimeoutError; | |
| module.exports = Promise; |