process-timer
Version:
High-resolution timer class for NodeJs & browsers
410 lines (382 loc) • 13.8 kB
JavaScript
/**
* @class ProcessTimer
* @provides High-resolution timer class for NodeJs & browsers
* Implements timestamps processing in a handy simple way
*
* @author https://juliyvchirkov.github.io
* @version 1.0.2 17/08/2018
* @release https://github.com/juliyvchirkov/process-timer/releases/tag/v1.0.2
* @bugs https://github.com/juliyvchirkov/process-timer/issues
* @license MIT
*
* @see README.md
*
* ProcessTimer {
* VERSION : '1.0.2',
* nsec : [Getter],
* usec : [Getter],
* msec : [Getter],
* sec : [Getter]
* ns : [Getter],
* us : [Getter],
* ms : [Getter],
* s : [Getter]
* }
*/
/**
* Universal module definition, turned into one-liner for simplicity & conciseness
*
* @see https://github.com/umdjs/umd
*/
;(function (global, factory) {
typeof define === 'function' && define.amd
? define(factory)
: typeof module === 'object' && module.exports
? (module.exports = factory)
: (global.ProcessTimer = factory)
})(this, function ProcessTimer (suffix) {
'use strict'
var CLASSID = 'ProcessTimer'
var VERSION = '1.0.2'
/**
* Defines or modifies a property of an object
*
* A TypeError is thrown on failure
*
* @param {object} object The object on which to define the property
* @param {string} property The name of the property to be defined or modified
* @param {object} descriptor The descriptor for the property being defined or
* modified
*
* @returns {object} The object that was passed to the function
*/
function defineProperty (object, property, descriptor) {
var data = 'value'
var accessorGet = 'get'
var accessorSet = 'set'
var errorPrefix = 'Cannot define or modify property “' + property + '” '
var errorMessage = null
/**
* Defines or modifies an accessor property of an object
*
* @param {string} id The name of the accessor property to be defined
* or modified (either “get” or “set”)
*
* @returns {void}
*/
function defineAccessor (id) {
var __define__ = '__define' + id[0].toUpperCase() + id.slice(1) + 'ter__'
var __lookup__ = '__lookup' + __define__.slice(8)
errorMessage = errorPrefix + id + 'ter'
try {
object[__define__](property, descriptor[id])
if (object[__lookup__](property) === descriptor[id]) {
errorMessage = null
}
} catch (error) {}
}
try {
Object.defineProperty(object, property, descriptor)
} catch (error) {
if (data in descriptor) {
try {
object[property] = descriptor[data]
} catch (error) {
} finally {
if (object[property] !== descriptor[data]) {
errorMessage = errorPrefix + data
}
}
} else {
accessorGet in descriptor && defineAccessor(accessorGet)
accessorSet in descriptor && defineAccessor(accessorSet)
}
} finally {
if (errorMessage) {
throw new TypeError(errorMessage)
} else {
return object
}
}
}
/**
* Defines or modifies properties of an object
*
* A TypeError is thrown on failure
*
* @param {object} object The object on which to define properties
* @param {object} properties An object whose own enumerable properties
* constitute descriptors for the properties
* to be defined or modified
*
* @returns {object} The object that was passed to the function
*/
function defineProperties (object, properties) {
var errorMessage = null
try {
Object.defineProperties(object, properties)
} catch (error) {
try {
for (var property in properties) {
defineProperty(object, property, properties[property])
}
} catch (error) {
errorMessage = error.message
}
} finally {
if (errorMessage) {
throw new TypeError(errorMessage)
} else {
return object
}
}
}
/**
* Makes an object immutable if Object.freeze method is available
*
* @param {object} object The object to freeze
*
* @returns {object} The object that was passed to the function
*/
function freeze (object) {
try {
Object.freeze(object)
} catch (error) {
} finally {
return object
}
}
/**
* Returns a string indicating the type of an object or a primitive
*
* A TypeError is thrown if invoked w/o arguments
*
* @param { … } item The object or primitive whose type is to be determined
*
* @returns {string} The type of the item that was passed to the function
*/
function typeOf (item) {
if (arguments.length === 0) {
throw new TypeError('Object or primitive has been expected, got nothing')
}
var itemTypeof = null
try {
itemTypeof = item.constructor.name
} catch (error) {
} finally {
return (
itemTypeof ||
Object.prototype.toString.call(item).slice(8, -1) ||
typeof item
).toLowerCase()
}
}
/**
* Returns a timer startpoint (a [seconds, nanoseconds] array tuple w/
* unique timestamp) if invoked w/o arguments. Further this array being
* passed back produces a diff (a [seconds, nanoseconds] array tuple w/
* time elapsed since a timer has been instantiated) on return
*
* A TypeError is thrown on anything but an array tuple
*
* @param {array} time The return of this function called w/o arguments
*
* @returns {array} A high-resolution timestamp in a [seconds,
* nanoseconds] array tuple, where nanoseconds
* is the remaining part of the timestamp that
* can't be represented in second precision
*/
function hrtime (time) {
try {
return process.hrtime(time)
} catch (error) {
time = time || [0, 0]
var timeTypeof = typeOf(time)
var timeTypeofArray = timeTypeof === 'array'
if (timeTypeofArray && time.length === 2) {
var now =
(typeof performance === 'object' &&
typeOf(performance) === 'performance' &&
typeOf(performance.now) === 'function'
? performance
: Date
).now() * 1e-3
var sec = Math.floor(now) - time[0]
var nsec = Math.floor((now % 1) * 1e9) - time[1]
var shift = (nsec < 0) * 1
return [sec - shift, nsec + shift * 1e9]
} else {
throw new TypeError(
'Array tuple has been expected, got ' +
(timeTypeofArray ? 'incompatible one' : timeTypeof)
)
}
}
}
/**
* The above shims have been designed to sand off some rough edges between
* NodeJs & various browser engines thru a number of modern & legacy methods
* sequenced within single functions in order to keep the main routine utterly
* simple & concise
*/
var classConstructor = 'Class constructor ' + CLASSID + ' '
if (!(this instanceof ProcessTimer)) {
throw new TypeError(classConstructor + 'cannot be invoked w/o “new”')
}
if (arguments.length !== 0 && typeof suffix !== 'string') {
throw new TypeError('String has been expected, got ' + typeOf(suffix))
}
/**
* Converts an integer of a high-resolution timestamp into a double
* according to the given precision
*
* Utilized to process ns, us & ms getters
*
* @param {number} stamp An integer of a high-resolution timestamp
* @param {number} depth The precision (either 9 or 6 or 3)
*
* @returns {number} A double of a high-resolution timestamp that
* was passed to the function
*/
function getHRSec (stamp, depth) {
var precision = Math.pow(10, -depth)
var sec = (stamp * precision).toFixed(depth)
return (sec * 1 - (sec.slice(-1) === '0' ? precision : 0)).toFixed(depth) * 1
}
/**
* The main routine
*/
try {
return freeze(
defineProperties(this, {
VERSION : {
enumerable : true,
value : VERSION
},
/**
* A suffix to append the return of ns, us, ms & s getters
* (a string that was passed to the constructor if any, null
* otherwise)
*/
suffix : {
value : typeOf(suffix) === 'string' ? suffix : null
},
/**
* A timer startpoint (a [seconds, nanoseconds] array tuple
* w/ unique timestamp)
*/
time : {
value : freeze(hrtime())
},
/**
* @getter nsec
*
* @returns {number} A number of nanoseconds elapsed since
* a timer has been instantiated
*/
nsec : {
enumerable : true,
get : function get () {
var time = hrtime(this.time)
return time[0] * 1e9 + time[1]
}
},
/**
* @getter usec
*
* @returns {number} A number of microseconds elapsed since
* a timer has been instantiated
*/
usec : {
enumerable : true,
get : function get () {
return Math.round(this.nsec * 1e-3)
}
},
/**
* @getter msec
*
* @returns {number} A number of milliseconds elapsed since
* a timer has been instantiated
*/
msec : {
enumerable : true,
get : function get () {
return Math.round(this.nsec * 1e-6)
}
},
/**
* @getter sec
*
* @returns {number} A number of seconds elapsed since a timer
* has been instantiated
*/
sec : {
enumerable : true,
get : function get () {
return Math.round(this.nsec * 1e-9)
}
},
/**
* @getter ns
*
* @returns {number|string} A number of seconds (accurate to
* nanoseconds) elapsed since a timer
* has been instantiated
*/
ns : {
enumerable : true,
get : function get () {
return getHRSec(this.nsec, 9) + this.suffix
}
},
/**
* @getter us
*
* @returns {number|string} A number of seconds (accurate to
* microseconds) elapsed since a timer
* has been instantiated
*/
us : {
enumerable : true,
get : function get () {
return getHRSec(this.usec, 6) + this.suffix
}
},
/**
* @getter ms
*
* @returns {number|string} A number of seconds (accurate to
* milliseconds) elapsed since a timer
* has been instantiated
*/
ms : {
enumerable : true,
get : function get () {
return getHRSec(this.msec, 3) + this.suffix
}
},
/**
* @getter s
*
* @returns {number|string} A number of seconds elapsed since
* a timer has been instantiated
*/
s : {
enumerable : true,
get : function get () {
return this.sec + this.suffix
}
}
})
)
} catch (error) {
throw new TypeError(
classConstructor +
'is unable to provide an instance (' +
error.message +
')'
)
}
})