Last active
May 23, 2018 07:55
-
-
Save softwarespot/18b6e783d6b0ea14232e0fa224d01d1e to your computer and use it in GitHub Desktop.
Basic zones implementation
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
// https://github.com/angular/zone.js/blob/2b3779d39a8f1930f59911c0d7e9e827f0fb5bc9/zone.js | |
// https://github.com/angular/zone.js/blob/d40a9376a825c702335d03e4122be8518e636791/zone.js | |
// https://github.com/angular/zone.js/blob/bee8a60d918fd6d737f8f525cb8d886f69bf06af/lib/zone.ts | |
// https://github.com/angular/zone.js/blob/3ad2445da420da2ede9cdb28c62986e86694a6fe/lib/core.ts | |
var Zone = (function Zone() { | |
// https://domenic.github.io/zones/#sec-zone-constructor | |
function Zone(parent, zoneSpec) { | |
zoneSpec = zoneSpec !== undefined ? zoneSpec : {}; | |
this.parent = parent; | |
this.name = zoneSpec.name !== undefined ? zoneSpec.name : 'unamed'; | |
inheritPropName(this, parent, zoneSpec, 'properties', isObject, {}); | |
inheritPropName(this, parent, zoneSpec, 'onHandleError', isFunction); | |
inheritPropName(this, parent, zoneSpec, 'onInvoke', isFunction); | |
} | |
Object.defineProperty(Zone, 'root', { | |
get: function root() { | |
var rootZone = currentZone; | |
while (rootZone.parent) { | |
rootZone = rootZone.parent; | |
} | |
return rootZone; | |
} | |
}); | |
// https://domenic.github.io/zones/#sec-get-zone.current | |
Object.defineProperty(Zone, 'current', { | |
get: function current() { | |
return currentZone; | |
} | |
}); | |
Zone.createLongStackTrace = function createLongStackTrace(zone, err) { | |
var traces = []; | |
while (zone) { | |
zone = zone.parent; | |
zone && traces.unshift(zone.trace); | |
} | |
traces.unshift(err); | |
return traces.join('\n'); | |
}; | |
// Zone prototype functionality | |
// https://domenic.github.io/zones/#sec-zone.prototype.run | |
Zone.prototype.run = function run(fn, applyThis, applyArgs) { | |
// applyThis = applyThis !== undefined ? applyThis : undefined; | |
// applyArgs = applyArgs !== undefined ? applyArgs : null; | |
var prevZone = currentZone; | |
currentZone = this; | |
try { | |
this.trace = createStackTrace(); | |
if (isFunction(this.onInvoke)) { | |
this.onInvoke(this, applyThis, applyArgs); | |
} | |
return fn.apply(applyThis, applyArgs); | |
} catch (ex) { | |
if (isFunction(this.onHandleError)) { | |
this.onHandleError(this, ex); | |
} else { | |
throw ex; | |
} | |
} finally { | |
currentZone = prevZone; | |
} | |
}; | |
// https://domenic.github.io/zones/#sec-zone.prototype.fork | |
Zone.prototype.fork = function fork(zoneSpec) { | |
if (!isObject(zoneSpec)) { | |
throw new Error('Invalid argument, expected "zoneSpec" to be an object'); | |
} | |
return new Zone(this, zoneSpec); | |
}; | |
// https://domenic.github.io/zones/#sec-zone.prototype.wrap | |
Zone.prototype.wrap = function wrap(fn) { | |
if (!isFunction(fn)) { | |
throw new Error('Invalid argument, expected "fn" to be a function'); | |
} | |
var zone = this; | |
return function wrapper() { | |
return zone.run(fn, this, arguments); | |
}; | |
}; | |
Zone.prototype.get = function get(key) { | |
var zone = this.getZoneWith(key); | |
if (zone) { | |
return zone.properties[key]; | |
} | |
return; | |
}; | |
Zone.prototype.getZoneWith = function getZoneWith(key) { | |
var zone = this; | |
while (zone) { | |
if (zone.properties.hasOwnProperty(key)) { | |
return zone; | |
} | |
zone = zone.parent; | |
} | |
return; | |
}; | |
// Utils | |
function isFunction(fn) { | |
return typeof fn === 'function'; | |
} | |
function isObject(obj) { | |
return Object(obj) === obj; | |
} | |
function createStackTrace() { | |
try { | |
// Error must be thrown to get stack in IE | |
throw new Error(); | |
} catch (ex) { | |
return ex.stack; | |
} | |
} | |
function inheritPropName(zone, parent, zoneSpec, propName, validatorFn, defaultValue) { | |
if (zoneSpec.hasOwnProperty(propName) && validatorFn(zoneSpec[propName])) { | |
zone[propName] = zoneSpec[propName]; | |
} else if (parent && parent.hasOwnProperty(propName) && validatorFn(parent[propName])) { | |
zone[propName] = parent[propName]; | |
} else { | |
zone[propName] = defaultValue; | |
} | |
} | |
function patchFnByArgIndex(obj, name, argIndex) { | |
var origFn = obj[name]; | |
obj[name] = function patchedFn() { | |
if (Zone.current) { | |
arguments[argIndex] = Zone.current.wrap(arguments[argIndex]); | |
} | |
return origFn.apply(obj, arguments); | |
}; | |
} | |
// Initialization | |
// Create a "<root>" zone with no parent | |
var currentZone = new Zone(undefined, { | |
name: '<root>' | |
}); | |
// Patching of global functionality | |
patchFnByArgIndex(global, 'setTimeout', 0); | |
patchFnByArgIndex(global, 'setInterval', 0); | |
return Zone; | |
}()); | |
// Append a global error handler (not spec compliant) | |
// Zone.current.onHandleError = function globalErrorHandler(zone, err) { | |
// console.error(`Error occurred for zone "${zone.name}", but was caught by the "global" handler`, err); | |
// }; | |
// Example | |
function main() { | |
console.log('Zone name in a sync invoked function', Zone.current.name); | |
global.setTimeout(() => { | |
console.log('Zone name in an async invoked function', Zone.current.name); | |
global.setTimeout(() => { | |
console.log(Zone.current.name, 'Start:', Zone.current.get('timestamp')); | |
anotherFn(true); | |
}); | |
// Create another zone inside a zone | |
Zone.current.fork({ | |
name: '(main inner)', | |
properties: { | |
anotherTimestamp: Date.now(), | |
} | |
}) | |
.run(() => { | |
anotherFn(); | |
// Inherits properties from parent, if "properties" is not defined in the "zoneSpec" i.e. passed to the fork() | |
console.log(Zone.current.name, 'Zone property which is not defined in fork:', Zone.current.get('timestamp')); | |
throw new Error(`An unexpected error occurred in the zone "${Zone.current.name}" (1)`); | |
}); | |
}); | |
} | |
function anotherFn(invokeError = false) { | |
console.log('Zone name in a sync invoked function', Zone.current.name); | |
// console.log('The root zone:', Zone.root); | |
global.setTimeout(() => { | |
console.log(Zone.current.name, 'Zone property:', Zone.current.get('timestamp')); | |
if (invokeError) { | |
throw new Error(`An unexpected error occurred in the zone "${Zone.current.name}" (2)`); | |
} | |
}, 200); | |
} | |
// Fork the "<root>" zone and continue invoking the zone in a different name | |
var zoneSpec = { | |
name: '(main)', | |
properties: { | |
timestamp: Date.now(), | |
}, | |
onInvoke(zone) { | |
// console.log(`"Run" function was invoked for zone "${zone.name}"`); | |
}, | |
onHandleError(zone, err) { | |
console.error(`Error occurred for zone "${zone.name}", but was caught by the "${zone.name}" error handler`, err); | |
// console.log('Long stack trace:', Zone.createLongStackTrace(zone, err)); | |
}, | |
}; | |
Zone.current.fork(zoneSpec) | |
.run(main); | |
// Invoked outside of the "(main)" zone i.e. "<root>" | |
// Note: Function is required for bind | |
global.setTimeout(function setTimeout() { | |
console.log('Zone name for outer zone i.e. "root"', Zone.current.name); | |
console.log(this.exampleOfBindingContext); | |
}.bind({ | |
exampleOfBindingContext: 123456, | |
})); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment