UNPKG

stagnant

Version:

Measure your slow code, make it _fast_.

227 lines (189 loc) 6.2 kB
function defaultConfig(){ function onevent(){} function onerror(){} function onsuccess(){} function onflush(){} // eslint-disable-next-line no-undef const ourConsole = { log(){}, error(){}, warn(){} } function generateId(){ return Math.random().toString(15).slice(2,8) } return { onevent, onerror, onsuccess, onflush, console: ourConsole, generateId } } function Main(config={}){ config = { ...defaultConfig(), ...config } const { generateId } = config async function dispatchEvent(event){ await config.onevent(event) if( event.error ) { await config.onerror(event) } else { await config.onsuccess(event) } } function Event({ parentId, id, startTime, endTime, name, data, error }){ return { parentId, id, startTime, endTime, name, data, error } } function RootEvent(){ const event = Event({ parentId: null ,id: generateId() ,startTime: Date.now() ,endTime: Date.now() ,error: null ,data: {} }) event.flush = async function flush(){ delete event.flush event.endTime = Date.now() await dispatchEvent(event) await config.onflush(event) return event } return event } function setupEvent({ parentEvent, name, data, sync }){ const event = Event({ parentId: parentEvent.id ,id: generateId() ,name ,startTime: null ,endTime: null ,data: { ...parentEvent.data || {}, ...data } ,error: null ,sync: parentEvent.sync || sync }) const childP = Instance(event) return { childP, event } } function Instance(parentEvent){ function handler(...args){ const callbackIndex = args.findIndex( x => typeof x == 'function' ) let cb = args[callbackIndex] let rest = callbackIndex > 0 ? args.slice(0,callbackIndex) : args let [name, data] = rest if (typeof name != 'string') { data = name if( cb ) { name = data.name || cb.toString().replace( /\s/g, '' ).replace( /(.)*=>/, '' ); } } if ( data == null ) data = {} return { name, data, callback: cb } } function handlerData({ data }){ parentEvent.data = { ...parentEvent.data, ...data } return null } async function handlerAsync({ event, childP, callback }){ if ( parentEvent.sync ) { throw new Error('Cannot use an async trace within a synchronous trace') } try { event.startTime = Date.now() const out = await callback(childP) event.endTime = Date.now() return out } catch (e) { event.endTime = Date.now() event.error = e throw e } finally { dispatchEvent(event) .catch( e => config.console.error('Failed to dispatch event', e)) } } function handlerSync({ callback, name, event, childP }){ try { event.startTime = Date.now() const out = callback(childP) event.endTime = Date.now() if( out != null && 'then' in out ) { config.console.warn(name, 'A call to trace.sync was made but the response was async. This is likely a mistake and should be corrected.') } return out } catch (e) { event.endTime = Date.now() event.error = e throw e } finally { dispatchEvent(event) .catch( e => config.console.error('Failed to dispatch event', e)) } } function routerOptions({ sync }, ...args){ const { data, callback, name } = handler(...args) const {event,childP} = callback ? setupEvent({ parentEvent, name, data, sync }) : {} if( callback && sync ) { return handlerSync({ data, callback, childP, name, event }) } else if ( callback && !sync ) { return handlerAsync({ data, callback, childP, name, event }) } else { return handlerData({ data }) } } async function routerAsync(...args){ const out = await routerOptions({ sync: false }, ...args) return out } function routerSync(...args){ const out = routerOptions({ sync: true }, ...args) return out } routerAsync.sync = routerSync return routerAsync } let rootEvent = RootEvent() let handlerInstance = Instance(rootEvent) handlerInstance.flush = rootEvent.flush handlerInstance.config = config return handlerInstance } /** * Safely invoke a callback even if trace is null. * * useful when there are multiple entry points into a function and some are not * passing in a trace. * * @param {*} trace * @param {...any} args * @returns */ function call(trace, ...args){ const cb = args.find( x => typeof x == 'function' ) if ( trace ) { return trace( ...args ) } else if ( cb ) { return cb( (...args) => call(null, ...args) ) } return null } Main.call = call /** * Create a no-op trace if provided trace is null * * Much like stagnant.call, useful when there are multiple entry points into a function and some are not * passing in a trace. * * @param {*} trace * @param {...any} args * @returns */ function ensure(trace){ if( trace ) { return trace } else { return (...args) => call(null, ...args) } } Main.ensure = ensure export default Main