itsa-utils
Version:
ITSA's utility functions
159 lines (147 loc) • 5.62 kB
JavaScript
/**
* Collection of various utility functions.
*
*
* <i>Copyright (c) 2014 ITSA - https://github.com/itsa</i>
* New BSD License - http://choosealicense.com/licenses/bsd-3-clause/
*
* @module utils
* @class Utils
* @static
*/
;
// NOTE: setTimeout can be up to 2147483647 milliseconds (the max for 32 bit integer: about 24 days
var TIMEOUT_MAX = (process.env.NODE_ENV==='test') ? 400 : 2147483647, // 2^31-1
_asynchronizer, _async, later;
/**
* Forces a function to be run asynchronously, but as fast as possible. In Node.js
* this is achieved using `setImmediate` or `process.nextTick`.
*
* @method _asynchronizer
* @param callbackFn {Function} The function to call asynchronously
* @static
* @private
**/
_asynchronizer = (typeof setImmediate !== "undefined") ? function (fn) {setImmediate(fn);} :
((typeof process !== "undefined") && process.nextTick) ? process.nextTick : function (fn) {setTimeout(fn, 0);};
/**
* Invokes the callbackFn once in the next turn of the JavaScript event loop. If the function
* requires a specific execution context or arguments, wrap it with Function.bind.
*
* I.async returns an object with a cancel method. If the cancel method is
* called before the callback function, the callback function won"t be called.
*
* @method async
* @param {Function} callbackFn
* @param [invokeAfterFn=true] {boolean} set to false to prevent the _afterSyncFn to be invoked
* @return {Object} An object with a cancel method. If the cancel method is
* called before the callback function, the callback function won"t be called.
**/
_async = function (callbackFn, invokeAfterFn) {
var canceled;
invokeAfterFn = (typeof invokeAfterFn === "boolean") ? invokeAfterFn : true;
(typeof callbackFn==="function") && _asynchronizer(function () {
if (!canceled) {
callbackFn();
}
});
return {
cancel: function () {
canceled = true;
}
};
};
var _setLongTimeout = function(cb, timeout) {
if (timeout<=TIMEOUT_MAX) {
return setTimeout(cb, timeout);
}
// else: use a long timeout by reuse the remaining the timeout
return setTimeout(_setLongTimeout.bind(null, cb, TIMEOUT_MAX-timeout), timeout);
};
/**
* Invokes the callbackFn after a timeout (asynchronous). If the function
* requires a specific execution context or arguments, wrap it with Function.bind.
*
* To invoke the callback function periodic, set "periodic" either "true", or specify a second timeout.
* If number, then periodic is considered "true" but with a perdiod defined by "periodic",
* which means: the first timer executes after "timeout" and next timers after "period".
*
* I.later returns an object with a cancel method. If the cancel() method is
* called before the callback function, the callback function won"t be called.
*
* @method later
* @param callbackFn {Function} the function to execute.
* @param [timeout] {Number} the number of milliseconds to wait until the callbackFn is executed.
* when not set, the callback function is invoked once in the next turn of the JavaScript event loop.
* @param [periodic] {boolean|Number} if true, executes continuously at supplied, if number, then periodic is considered "true" but with a perdiod
* defined by "periodic", which means: the first timer executes after "timeout" and next timers after "period".
* The interval executes until canceled.
* @return {object} a timer object. Call the cancel() method on this object to stop the timer.
*/
var later = function (callbackFn, timeout, periodic) {
var canceled = false;
if (typeof timeout!=="number") {
return _async(callbackFn);
}
var wrapper = function() {
// nodejs may execute a callback, so in order to preserve
// the cancel() === no more runny-run, we have to build in an extra conditional
if (!canceled) {
callbackFn();
// we are NOT using setInterval, because that leads to problems when the callback
// lasts longer than the interval. Instead, we use the interval as inbetween-phase
// between the separate callbacks.
id = periodic ? _setLongTimeout(wrapper, (typeof periodic==="number") ? periodic : timeout) : null;
}
},
id;
(typeof callbackFn==="function") && (id=_setLongTimeout(wrapper, timeout));
return {
cancel: function() {
canceled = true;
id && clearTimeout(id);
// break closure:
id = null;
}
};
};
/**
* Creates an synchronous delay: returns a Promise
*
* @example
*
* var delay = require("itsa-utils").delay;
*
* var someFn = async () => {
* await delay(1000);
* // code here executes after 1 second delay
* }
*
* @method delay
* @param [timeout=0] {Number} ms to delay.
* @return {Promise} A Promise which will fulfill after the ms set in the first argument.
*/
var delay = function(ms) {
ms || (ms=0);
return new Promise(function(fulfill) {
later(fulfill, ms);
});
};
/**
* Invokes the callbackFn once in the next turn of the JavaScript event loop. If the function
* requires a specific execution context or arguments, wrap it with Function.bind.
*
* I.async returns an object with a cancel method. If the cancel method is
* called before the callback function, the callback function won"t be called.
*
* @method async
* @param {Function} callbackFn
* @param [invokeAfterFn=true] {boolean} set to false to prevent the _afterSyncFn to be invoked
* @return {Object} An object with a cancel method. If the cancel method is
* called before the callback function, the callback function won"t be called.
**/
module.exports = {
async: _async,
later: later,
delay: delay
};