promisesds
Version:
ES6 Promises data structures and utils
1,019 lines (931 loc) • 35 kB
JavaScript
var PromisesDS =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
module.exports = {
LastAction: __webpack_require__(1),
OrderedPromises: __webpack_require__(2),
PromiseCache: __webpack_require__(3),
Sequence: __webpack_require__(4)
};
/***/ },
/* 1 */
/***/ function(module, exports) {
module.exports = function() {
"use strict";
var noop = function(){};
/**
* The LastAction object accepts actions (functions that return promises) only executing the last
* action available and dropping the rest. The object also waits for a executed action to complete before
* executing the next one.
* Note: This is a only client side solution to ordering actions, more network efficient solutions
* can be achieved with server collaboration, sequence numbers, acks...
*
* @param {Function} onComplete Executes when an action completes successfully and no action is
* waiting to be executed
* @param {Function} onError Executes when an action fails and no action is
* waiting to be executed
* @param {Int} retries Number of retries for each action before failing, default: 0
* @return {LastAction} LastAction instance
*/
var LastAction = function(onComplete, onError, retries) {
this.onError = onError || noop;
this.onComplete = onComplete || noop;
this.retries = retries || 0;
this._deferred = null;
this.lastAction = null;
};
/**
* Aux function to emulate jQuery deferred functionality
* @return {Object} Object with resolve and reject methods and a promise property
*/
var deferred = function(){
var dfr;
var promise = new Promise(function(resolve, reject){
dfr = {
resolve: resolve,
reject: reject
};
});
dfr.promise = promise;
return dfr;
};
/**
* Function for DRY, takes an action response and cleans it's deferred
* or resolved the next one if exists
* @param {LastAction} self Instance
* @param {mixed} response Action response
* @param {Deferred} dfr Action deferred
* @param {Function} callback To call if last Action
* @return {mixed} Chain response
*/
var resolver = function(self, response, dfr, callback) {
if (dfr === self._deferred) {
self._deferred = null;
self._lastResponse = response;
callback(response);
} else {
self._deferred.resolve(response);
}
return response;
};
/**
* Function for DRY. Executes an action and calls resolver in case of success or error
* @param {LastAction} self Instance
* @param {Function} action action to execute
* @param {Deferred} dfr Pass along to the resolver
* @return {promise} Filtered after resolver action promise
*/
var actionExecuter = function(self, action, dfr) {
return action().then(function(response) {
return resolver(self, response, dfr, self.onComplete);
}, function(response) {
throw resolver(self, response, dfr, self.onError);
});
};
/**
* Recursively handles actions retries, dropping retries if a newer action
* is available
* @param {LastAction} self Instance
* @param {Function} action Action to execute
* @param {Int} retries Number of times to retry the action
* @param {Deferred} dfr Resolved when retries are done
* @return {Promise} Promise that resolves on success or is rejected when out of retries
*/
var retrier = function(self, action, retries, dfr){
push(self, action).then(function(response){
dfr.resolve(response);
}, function(response) {
if (self._deferred === null && retries > 0) {
retrier(self, action, retries - 1, dfr);
} else {
dfr.reject(response);
}
});
};
/**
* Checks if there is an action to wait for and sets self._deferred so when the action
* is over this can be triggered. Or executes the action immediately.
* @param {LastAction} self instance
* @param {Function} action Action to execute
* @return {Promise} Resolves when the actions finishes (if it does)
*/
var push = function(self, action) {
self.lastAction = action;
var dfr = deferred();
if (self._deferred) {
self._deferred = dfr;
return self._deferred.promise.then(function(response) {
return actionExecuter(self, action.bind(null, response), dfr);
});
} else {
self._deferred = dfr;
return actionExecuter(self, action.bind(null, self._lastResponse), dfr);
}
};
LastAction.prototype = {
/**
* Adds an action to be executed if no other action is added before the last one finishes
* @param {Function} action Function that returns an action, receives a parameter from the lastPromise
* action executed or null
* @param {Int} retries Number of retries for this action (overrides the default on the constructor)
* @return {Promise} Promise that resolves if the action is actually executed and resolves.
*/
push: function(action, retries) {
retries = retries === undefined ? this.retries : retries;
var dfr = deferred();
retrier(this, action, retries, dfr);
return dfr.promise;
},
/**
* Last action that was added to this instance of LastAction or null if no action has been added
* @return {Function} action
*/
lastAction: function() {
return this.lastAction;
}
};
return LastAction;
}();
/***/ },
/* 2 */
/***/ function(module, exports) {
module.exports = function() {
"use strict";
/**
* The orderedPromises object keeps an ordered list of promises for a single
* resource. It provides a next callback that is whenever a more updated
* result is available. This guarantees the order of the results preventing
* old results getting mixed with newer ones.
* The ordering is only kept on the client side so this is ideal for stateless
* requests.
* @param {array} promises An initial list of promises to track, be careful
* to initializw the callbacks with options if the promises
* may have already been completed
* @param {Object} options {
* next: @see orderedPromises.next();
* last: @see orderedPromises.next();
* discarded: @see orderedPromises.next();
* }
*/
var OrderedPromises = function(promises, options){
var self = this;
options = options || {};
promises = promises || [];
this._promises = [];
this.next(options.next, options.nextFail);
this.last(options.last, options.lastFail);
this.discarded(options.discarded);
promises.forEach(function(promise){
self.push(promise);
});
};
/**
* Private function to go over the promises array when a new one is completed
* and discard old promises and call the next callback and the last callback
* if it's the last one
* @param {OrderedPromises} self instance of the object
* @param {[]} promises array of promises
* @param {Promise} promise Promise that has completed
* @param {arguments} argsObj args of the solved promise
* @param {boolean} success if the promise completed successfully
*/
function process(self, promises, promise, value, success){
while (promises.length){
var current = promises.shift();
if(promise !== current.promise){
current.discarded = true;
self._discarded(current.promise);
} else {
self[success ? '_next' : '_nextFail'](promise, value);
if (!promises.length){
self[success ? '_last' : '_lastFail'](promise, value);
}
return;
}
}
}
var noop = function(){};
OrderedPromises.prototype = {
/**
* Add a new promise to the list.
* @param {Promise} promise to add
* @return {OrderedPromises} chainable return
*/
push: function(promise){
var self = this;
var obj = {
promise: promise,
discarded: false
};
this._promises.push(obj);
promise.then(function(value){
if (!obj.discarded){
process(self, self._promises, obj.promise, value, true);
}
}, function(value){
if (!obj.discarded){
process(self, self._promises, obj.promise, value, false);
}
});
return this;
},
/**
* Callback triggered when the last promise in the list is completed
* @param {Function} handler handler for a successfully completed promise
* @see next for the callback specification
* @param {Function} failure handler for a rejected promise
* @see last for the callback specification
* @return {OrderedPromises} chainable return
*/
last: function(handler, failure){
this._last = handler || noop;
this._lastFail = failure || noop;
return this;
},
/**
* Callback triggered when a more updated result is available, that is, no promise
* after this has been completed. Just before this callback is executed the previous
* not yet completed promises are discarded.
* @param {Function} handler handler for a successfully completed promise
* handler(promise, promise_resolved_args...)
* @param {Function} failure handler for a rejected promise
* handler(promise, promise_rejected_args...)
* @return {OrderedPromises} chainable return
*/
next: function(handler, failure){
this._next = handler || noop;
this._nextFail = failure || noop;
return this;
},
/**
* It creates a copy of the promises array currently on the object, it contains
* all promises introduced that have not been discarded or completed
* @return {[]} array of promises
*/
promises: function(){
return this._promises.map(function(obj){
return obj.promise;
});
},
/**
* Callback for the discarded promises so it's possible to keep track of the
* promises that didn't complete before a more updated result was available.
* It may be useful to release resources or abort them.
* @param {Function} handler handler(promise)
* @return {OrderedPromises} chainable return
*/
discarded: function(handler){
this._discarded = handler || noop;
return this;
}
};
return OrderedPromises;
}();
/***/ },
/* 3 */
/***/ function(module, exports) {
module.exports = function() {
"use strict";
/**
* Partial insertion sort function implemented to create a sorted array
* of promises to evict on one pass
* @param {array[Promise]} promises set of promises to scan
* @param {int} nEvicted number of promises to return
* @param {string} prop key name of the counter on the promise
* @param {Boolean} desc To invert the sort order
* @return {array[Promise]} promises to evict
*/
var evictionSort = function(promises, nEvicted, prop, desc){
desc = Boolean(desc);
var limit;
if (promises[0]) {
limit = promises[0][prop];
}
var evicted = [];
var evictedSize = 0;
Object.keys(promises).forEach(function(key){
var promise = promises[key];
if (desc === (limit <= promise[prop]) || evictedSize < nEvicted) {
var i = 1;
var notFilled = i < nEvicted && !evicted[i];
while (notFilled || (evicted[i] && desc === (evicted[i][prop]) <= promise[prop])) {
evicted[i - 1] = evicted[i];
i += 1;
notFilled = i < nEvicted && !evicted[i];
}
evicted[i - 1] = {propKey: key};
evicted[i - 1][prop] = promise[prop];
evictedSize += 1;
limit = evicted[0] ? evicted[0][prop] : limit;
}
});
return evicted;
};
/**
* Least recent used cache eviction implementation
* @see PromiseCache::evict(int)
*/
var Lru = (function () {
var LruCons = function () {
this.counter = 0;
};
LruCons.prototype.init = function (cache) {
this.cache = cache;
};
LruCons.prototype.set = function (_key, promise) {
promise.lru = 0;
};
LruCons.prototype.get = function (_key, promise) {
this.counter += 1;
promise.lru = this.counter;
};
/**
* The evict method is somewhat costly since get a set are
* trivial, it finds nEvicted elements in a pass over the cache.
* @param {int} nEvicted number of elements to evict from the cache
*/
LruCons.prototype.evict = function (nEvicted) {
var cache = this.cache;
var evicted = evictionSort(cache._promises, nEvicted, 'lru');
Object.keys(evicted).forEach(function (key) {
cache.remove(evicted[key].propKey);
});
};
return LruCons;
})();
/**
* Most recent used eviction algorithm
* @see PromiseCache::evict()
*/
var Mru = (function () {
var MruCons = function () {
this.counter = 0;
};
MruCons.prototype.init = function (cache) {
this.cache = cache;
};
MruCons.prototype.set = function (_key, promise) {
promise.mru = 0;
};
MruCons.prototype.get = function (_key, promise) {
this.counter += 1;
promise.mru = this.counter;
};
/**
* @see Lru::evict(int)
* @param {int} nEvicted number of elements to evict from the cache
*/
MruCons.prototype.evict = function (nEvicted) {
var cache = this.cache;
var evicted = evictionSort(cache._promises, nEvicted, 'mru', true);
Object.keys(evicted).forEach(function (key) {
cache.remove(evicted[key].propKey);
});
};
return MruCons;
})();
/**
* Least frequently used eviction algorithm
* @see PromiseCache::evict()
*/
var Lfu = (function () {
var LfuCons = function () {};
LfuCons.prototype.init = function (cache) {
this.cache = cache;
};
LfuCons.prototype.set = function (_key, promise) {
promise.lfu = 0;
};
LfuCons.prototype.get = function (_key, promise) {
promise.lfu += 1;
};
/**
* @see Lru::evict(int)
* @param {int} nEvicted number of elements to evict from the cache
*/
LfuCons.prototype.evict = function (nEvicted) {
var cache = this.cache;
var evicted = evictionSort(cache._promises, nEvicted, 'lfu');
Object.keys(evicted).forEach(function (key) {
cache.remove(evicted[key].propKey);
});
};
return LfuCons;
})();
/**
* Map from algorithms names to classes.
* @type {Object}
*/
var algorithms = {
lru: Lru,
mru: Mru,
lfu: Lfu
};
/**
* Aux function to emulate jQuery deferred functionality
* @return {Object} Object with resolve and reject methods and a promise property
*/
var deferred = function(){
var dfr;
var promise = new Promise(function(resolve, reject){
dfr = {
resolve: resolve,
reject: reject
};
});
dfr.promise = promise;
return dfr;
};
var noop = function () {};
/**
* The promise cache is a small cache implementation for with some features
* to manage promises as failure management and expire time.
* It has an eviction interface that decouples the algorithm and offers LRU,
* MRU and LFU implementations.
* The Deferred objects have a resolve and reject method that manages the underlying promise
* @param {Object[key- > promise]} promises Initial set of promises to cache with the keys
* present in the object
* @param {Object} options {
* eviction Object|string: eviction algorithm
* ('lru', 'mru', 'lfu') or object implementing
* the eviction interface @see PromiseCache::evict(int)
* capacity int: Cache max number of promises, it will call
* evict when full
* evictRate int: Number of promises to evict when the cache
* is full, it may be more efficient if the eviction algorithm
* is costly.
* discarded function(key, promise): optional default function
* @see PromiseCache::set
* expireTime int: optional default number of seconds before
* the promise is removed from the cache
* fail function(dfr: Deferred, key, promise): optional default
* function @see PromiseCache::set
* }
*/
var PromiseCache = function (promises, options) {
options = options || {};
this._promises = {};
this.length = 0;
var eviction;
if (options.eviction) {
if (algorithms[options.eviction]) {
eviction = new algorithms[options.eviction]();
} else {
eviction = options.eviction;
}
}
eviction = eviction || {};
eviction.init = eviction.init || noop;
eviction.get = eviction.get || noop;
eviction.set = eviction.set || noop;
eviction.remove = eviction.remove || noop;
if (options.capacity === undefined) {
eviction.evict = eviction.evict || noop;
} else if (!eviction.evict) {
throw new Error('There is a capacity but no evict function set');
}
this.eviction = eviction;
this.eviction.init(this, promises, options);
this.capacity = options.capacity;
this.evictRate = options.evictRate || 1;
this.discarded = options.discarded;
this.expireTime = options.expireTime;
this.fail = options.fail;
var self = this;
Object.keys(promises || {}).forEach(function(key){
self.set(key, promises[key], options);
});
};
/**
* Sets a promise in the cache with key and options that override the default versions,
* can trigger eviction if capacity is exceeded.
* @param {string} key to access the cached promise
* @param {Promise} promise Promise to save in the cache
* @param {Object} options { This options override the defaults available in the constructor
* discarded function(key, promise): optional, function to be called
* when the element is removed from the cache
* expireTime int: optional, number of seconds before the promise is
* removed from the cache
* fail function(dfr: Deferred, key, promise): optional, if present
* or the default exists a new promise will be created and set in
* the cache, this promise will be succeed when the original is
* resolved.
* If the original promise is rejected the fail function will be
* called and dfr can be used to resolve or reject the new promise
* that all the users of the get method have.
* This allow the setter to centralize error handling and
* potentially provide transparent retries and recovery procedures
* for the getters.
* }
*/
PromiseCache.prototype.set = function (key, promise, options) {
if (promise) options = options || {};
var self = this;
var interceptor;
if (!promise || !promise.then) {
throw new Error('promise: ' + promise + ' is not a Promise');
}
if (!this._promises[key]){
this.length += 1;
if (this.capacity < this.length) {
this.evict(this.evictRate);
}
}
var fail = options.fail || this.fail;
var promiseObj;
if (fail) {
var dfr = deferred();
interceptor = dfr.promise;
promiseObj = {
promise: interceptor
};
this._promises[key] = promiseObj;
promise.then(function (value) {
dfr.resolve(value);
}, function () {
fail(dfr, key, promise);
});
} else {
promiseObj = {
promise: promise
};
this._promises[key] = promiseObj;
promise.then(null, function () {
if (self._promises[key] && self._promises[key] === promiseObj) {
this.remove(key);
}
});
}
this._promises[key].discarded = options.discarded || this.discarded || noop;
var expireTime = options.expireTime !== undefined ? options.expireTime : this.expireTime;
if (expireTime !== undefined) {
setTimeout(function () {
if (self._promises[key] && self._promises[key] === promiseObj) {
self.remove(key);
}
}, expireTime);
}
this.eviction.set(key, promiseObj, promise, options);
};
/**
* Remove the key from the cache and calls the discarded callback if it exists, it is called
* by the eviction algorithms when clearing the cache.
* @param {string} key cache entry to remove
* @return {Promise|undefined} Removed promise or undefined it it doesn't exist
*/
PromiseCache.prototype.remove = function (key) {
var promise = this._promises[key];
if (promise !== undefined) {
delete this._promises[key];
this.length -= 1;
this.eviction.remove(key, promise);
promise.discarded(key, promise.promise);
return promise.promise;
}
};
/**
* Retrieves the promise in the cache stored with the key
* @param {string} key cache entry to retrieve
* @return {Promise|undefined} Promise stored with the key or undefined if it doesn't exist
*/
PromiseCache.prototype.get = function (key) {
if (this._promises[key]) {
this.eviction.get(key, this._promises[key]);
return this._promises[key].promise;
}
};
/**
* Object containing all promises in the cache with their keys, the object is an independent
* copy of the internal promises store.
* @return {Object} promises
*/
PromiseCache.prototype.promises = function () {
var cleanCopy = {};
var promises = this._promises;
Object.keys(promises).forEach(function(key){
cleanCopy[key] = promises[key].promise;
});
return cleanCopy;
};
/**
* Will remove nEvicted promises from the cache or all if larger than the number of promises
* @param {int} nEvicted number of cache entries to clear
*/
PromiseCache.prototype.evict = function (nEvicted) {
this.eviction.evict(nEvicted);
};
return PromiseCache;
}();
/***/ },
/* 4 */
/***/ function(module, exports) {
module.exports = function() {
"use strict";
/**
* Abstracts a sequence of a asynchronous actions, the order of execution of the
* different actions is enforced using deferred objects.
* The successful completion of an action will trigger the start of the next one.
* If an action fails the following actions will fail too until an action with
* fallback is found in the queue (the fallback action will be called then).
*
* Actions consist of a function that receives a Deferred object as its first
* parameter and the result of the previous action as the following parameters.
*
* A Deferred object consists of a resolve and reject methods that manage the underlying
* promise
*
* Actions are pushed using the available methods or using an array when
* the sequence is created.
* For every push feature there is an object syntax using properties and a
* method and parameters syntax. Additional features include pushing promises,
* setting timeouts for the sequence to reach a point and executing actions
* when the queue is empty.
*
* @param {array[Object|function]} actions An array with the inital actions to
* execute in the secuence using
* object syntax:
* Function: action to execute. The sequence will continue when it resolves
* its Deferred object.
* {action, fallback}: action and fallback in case of failure of the
* previous action.
* {promise}: promise that will stop the secuence untils it's completed
* {synchronous}: action executed synchronously without the need to resolve
* the deferred object.
* {timeout, duration}: action to execute if the Sequence has not
* reached that point after duration.
* {whenEmpty, fallback}: action to execute when the sequence has no
* pending actionsto execute.
*/
var Sequence = function (actions) {
var self = this;
this.lastPromise = Promise.resolve();
if (Array.isArray(actions)) {
actions.forEach(function (action) {
self.pushObject(action);
});
} else if (actions !== undefined) {
throw new Error('actions (if passed) must be an array');
}
};
/**
* Aux function to emulate jQuery deferred functionality
* @return {Object} Object with resolve and reject methods and a promise property
*/
var deferred = function() {
var dfr;
var promise = new Promise(function(resolve, reject) {
dfr = {
resolve: resolve,
reject: reject
};
});
dfr.promise = promise;
return dfr;
};
/**
* Adds an action with object syntax @see Sequence(actions)
* @param {Object} obj action or feature to add to the sequence
* @return {Sequence} current instance to allow chaining
*/
Sequence.prototype.pushObject = function (obj) {
if (obj && obj.call) {
this.push(obj);
} else if (obj.action) {
this.push(obj.action, obj.fallback);
} else if (obj.timeout) {
this.setTimeout(obj.timeout, obj.duration);
} else if (obj.whenEmpty) {
this.whenEmpty(obj.whenEmpty, obj.fallback);
} else if (obj.promise) {
this.pushPromise(obj.promise);
} else if (obj.synchronous) {
this.pushSynchronous(obj.synchronous, obj.fallback);
} else {
var err = new Error('action not recognized ' + obj);
err.action = obj;
throw err;
}
return this;
};
/**
* [private] Pipes the resolveWith and rejectWith of two promises so when the origin
* is completed the target completes too. Checks if origin is a promise
* @param {Promise} origin promise to attach the callbacks to pipe the completion
* @param {Deferred} target deferred linked to the origin promise
*/
var pipeResolve = function (origin, target) {
if (origin && origin.then && origin.then.call) {
origin.then(function (value) {
target.resolve(value);
}, function (value) {
target.reject(value);
});
}
};
/**
* Main method to add actions to the sequence pushes actions at the end of
* the sequence that will be executed when all the previous ones are resolved.
* The fallback method is called if the previous action failed.
* @param {Function} action Action to execute
* (action(deferred, [args,]) : result)
* deferred: Deferred object that will trigger the next action if
* completed succesfully or call the next fallback if rejected. The
* arguments passed when resolved will be passed to the next action or
* fallback.
* args: optional arguments sent by the previous action.
* result: optional Deferred that will resolve the action instead of the
* parameter deferred
* @param {Function} fallback Action to execute if the last action failed
* (fallback(deferred, [args,]) : result)
* deferred: Deferred object that will trigger the next action if
* completed succesfully or call the next fallback if rejected. The
* arguments passed when resolved will be passed to the next action or
* fallback.
* args: optional arguments sent by the previous action.
* result: optional Deferred that will resolve the action instead of the
* parameter deferred
* @return {Sequence} current instance to allow chaining
*/
Sequence.prototype.push = function (action, fallback) {
var nextDeferred = deferred();
var oldPromise = this.lastPromise;
this.lastPromise = nextDeferred.promise;
delete nextDeferred.promise;
oldPromise.then(function (value) {
var result = action(nextDeferred, value);
pipeResolve(result, nextDeferred);
});
if (fallback) {
oldPromise.then(null, function (value) {
var result = fallback(nextDeferred, value);
pipeResolve(result, nextDeferred);
});
} else {
oldPromise.then(null, function (value) {
nextDeferred.reject(value);
});
}
return this;
};
/**
* Pushes a promise into the sequence, the sequence cannot control
* the start of the action but guarantees that the next action will not
* be executed until all the previous ones and the promise completes
* @param {Promise} promise Promise to introduce in the sequence
* @return {Sequence} current instance to allow chaining
*/
Sequence.prototype.pushPromise = function (promise) {
var oldPromise = this.lastPromise;
this.push(function (deferred) {
oldPromise.then(function () {
promise.then(function (value) {
deferred.resolve(value);
}, function (value) {
deferred.reject(value);
});
}, function (value) {
promise.always(function () {
deferred.reject(value);
});
});
});
return this;
};
/**
* Adds an action and a fallback to be executed synchronously.
* The function don't receive a deferred object and they will execute
* when the previous action completes and the next action will be triggered
* as soon as the function exits.
* Synchronous actions cannot stop the execution of the next action.
* @param {Function} action Action to execute if the previous one completes
* successfully.
* (action([args,]) : result)
* args: optional arguments sent by the previous action.
* result: optional return value sent to the next action
* @param {Function} fallback Action to execute if the previous one fails.
* (fallback([args,]) : result)
* args: optional arguments sent by the previous actions.
* result: optional return value sent to the next action
* @return {Sequence} current instance to allow chaining
*/
Sequence.prototype.pushSynchronous = function (action, fallback) {
this.push(function (deferred, value) {
var result = action(value);
deferred.resolve(result);
}, function (deferred, value) {
var result = fallback(value);
deferred.resolve(result);
});
return this;
};
/**
* Sets a timeout at the current position in the sequence if the timeout
* expires before the previous action completes the handler will be fired
* as if it were a regular action. If the previous action completes before
* the timeout expires the next action will be executed and the timeout
* handler will never be called.
* @param {Function} handler Action to execute if the timeout expires
* (handler(deferred) : result)
* @see Sequence.push() action parameter
* @param {Int} duration Milliseconds to wait before triggering
* the timeout handler
* @return {Sequence} current instance to allow chaining
*/
Sequence.prototype.setTimeout = function (handler, duration) {
var timeoutDfr = deferred();
var timeoutFired = false;
var id = setTimeout(function () {
timeoutFired = true;
var result = handler(timeoutDfr);
pipeResolve(result, timeoutDfr);
}, duration);
var oldPromise = this.lastPromise;
this.lastPromise = timeoutDfr.promise;
var pipeDfr = function (method) {
return function (value) {
if (!timeoutFired) {
clearTimeout(id);
method(value);
}
};
};
oldPromise.then(pipeDfr(timeoutDfr.resolve), pipeDfr(timeoutDfr.reject));
return this;
};
/**
* Adds an action that will be executed when the sequence has no
* more actions to execute. If actions are added after whenEmpty action
* is added but before it is executed they will be executed before.
* whenEmpty action will be executed at most once (if actions keep being
* added or are not resolved it can starve)
* @param {Function} action Action to execute if the last action succeed
* (action(deferred, [args,]) : result)
* @see Sequence.push() action parameter
* @param {Function} fallback Action to execute if the last action failed
* (fallback(deferred, [args,]) : result)
* @see Sequence.push() fallback parameter
* @return {Sequence} current instance to allow chaining
*/
Sequence.prototype.whenEmpty = function (action, fallback) {
var currentPromise = this.lastPromise;
var self = this;
var pipeActions = function (func) {
return function (value) {
if (self.lastPromise === currentPromise) {
var nextDeferred = deferred();
self.lastPromise = nextDeferred.promise;
var result = func(nextDeferred, value);
pipeResolve(result, nextDeferred);
} else {
currentPromise = self.lastPromise;
currentPromise.then(pipeActions(action), pipeActions(fallback));
}
};
};
currentPromise.then(pipeActions(action), pipeActions(fallback));
return this;
};
/**
* Returns the promise that will be resolved by the last action currently
* in the sequence.
* @return {Promise} promise of the last action in the sequence
*/
Sequence.prototype.promise = function () {
return this.lastPromise;
};
return Sequence;
}();
/***/ }
/******/ ]);