Last active
May 29, 2018 20:39
-
-
Save softwarespot/481e1cfac06ff22ffa297c57bc2a31a3 to your computer and use it in GitHub Desktop.
Guard a function and execute a function guarded with optional zone information
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Guard a function, with optional zone information. | |
// Note: Any function which is guarded inside an already guarded function | |
// and does not provide any zone information, will inherit from the outer | |
// guarded function | |
const defaultName = '<root>'; | |
guard._zone = { | |
name: defaultName, | |
// Hidden property, due to being prefixed with "$" | |
$names: defaultName, | |
}; | |
function guard(fn, zone = { }) { | |
// Check if the function is already guarded | |
// if (fn.guard) { | |
// return fn.guard; | |
// } | |
// Check if the function is a guard function, | |
// as it will contain the properties "zone" and "previous" | |
if (fn.zone || fn.previous) { | |
return fn; | |
} | |
guarded.previous = guard.current; | |
// Clone the zone provided, and transfer properties from the currently active zone | |
// i.e. if the name is not provided in the provided "zone" argument, then it will be inherited from the currently active zone | |
guarded.zone = Object.assign({}, guard.current, zone); | |
if (guard.current.name !== guarded.zone.name) { | |
// Ensures that "$names" property is private and can't be overridden | |
guarded.zone.$names = `${guard.current.$names} ${guarded.zone.name}`; | |
} | |
// appendStackTrace(guarded.zone); | |
function guarded(...args) { | |
guard._zone = guarded.zone; | |
try { | |
return fn.apply(guarded.zone.context, args); | |
} catch (ex) { | |
if (typeof guard.onHandleError === 'function') { | |
guard.onHandleError(ex, guarded.zone, guarded.zone.$names); | |
} else { | |
console.error(ex, guarded.zone); | |
} | |
} finally { | |
guard._zone = guarded.previous; | |
} | |
} | |
// fn.guard = guarded; | |
return guarded; | |
} | |
Object.defineProperty(guard, 'current', { | |
get() { | |
return guard._zone; | |
} | |
}); | |
function appendStackTrace(zone) { | |
try { | |
// For IE11 only | |
throw new Error(); | |
} catch (ex) { | |
zone.stack = ex.stack; | |
} | |
} | |
// Helper function for immediately invoking a function that will be guarded | |
function runGuarded(fn, args = [], zone = {}) { | |
return guard(fn, zone)(...args); | |
} | |
// Register a global event handler. | |
// Note: This is optional | |
guard.onHandleError = (err, zone, names) => { | |
console.error('Global handler: ', err, ' => ', 'Zone name:', zone.name, 'Zone names:', names); | |
console.log(zone); | |
// The current zone should equal the zone which threw an exception | |
console.log(guard.current === zone); | |
// console.error(err.stack); | |
}; | |
// Example(s) | |
const zone1 = { | |
name: '<root 1>', | |
context: { foo: 'bar', }, | |
}; | |
runGuarded(() => { | |
setTimeout(guard(() => { | |
throw new Error('An unexpected error occurred in async function (1.1), zone name: <root1>, zone names: <root> <root1>'); | |
}), 100); | |
throw new Error('An unexpected error occurred (1.0), zone name: <root1>, zone names: <root> <root1>'); | |
}, undefined, zone1); | |
const zone2 = { | |
name: '<root 2>', | |
context: { baz: 'biz', }, | |
}; | |
runGuarded(() => { | |
setTimeout(guard(() => { | |
setTimeout(guard(() => { | |
throw new Error('An unexpected error occurred in async function (2.2), zone name: <root2>, zone names: <root> <root2>'); | |
}), 100); | |
throw new Error('An unexpected error occurred (2.1), zone name: <root2>, zone names: <root> <root2>'); | |
}), 100); | |
runGuarded((...args) => { | |
console.log(args); | |
setTimeout(guard(() => { | |
// Guarded async function inside another guarded async function | |
setTimeout(guard(() => { | |
throw new Error('An unexpected error occurred in async function (3.2), zone name: <root3>, zone names: <root> <root2> <root3>'); | |
}), 200); | |
throw new Error('An unexpected error occurred in async function (3.1), zone name: <root3>, zone names: <root> <root2> <root3>'); | |
}), 200); | |
throw new Error('An unexpected error occurred (3.0), zone name: <root3>, zone names: <root> <root2> <root3>'); | |
}, [1, 2, 3, 4, 5, 6, 7, 8, 9, 0], { | |
name: '<root 3>' | |
}); | |
throw new Error('An unexpected error occurred (2.0), zone name: <root2>, zone names: <root> <root2>'); | |
}, [], zone2); | |
runGuarded((...args) => { | |
console.log(args); | |
throw new Error('An unexpected error occurred (0.0), zone name: <root>, zone names: <root>'); | |
}, [10, 20, 30, 40, 50, 60, 70, 80, 90]) | |
const origFn = () => {}; | |
const orignFn2 = () => {}; | |
const guardedFn = guard(origFn); | |
// Check it's not re-guarding a function which is already guarded | |
console.assert(guardedFn !== guard(origFn)); | |
console.assert(guardedFn === guard(guardedFn)); | |
console.assert(guard(origFn) !== guard(origFn)); | |
console.assert(guard(guardedFn) === guard(guardedFn)); | |
console.assert(guard(orignFn2) !== guard(orignFn2)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment