Skip to content

Instantly share code, notes, and snippets.

@softwarespot
Last active May 23, 2018 07:55
Show Gist options
  • Save softwarespot/18b6e783d6b0ea14232e0fa224d01d1e to your computer and use it in GitHub Desktop.
Save softwarespot/18b6e783d6b0ea14232e0fa224d01d1e to your computer and use it in GitHub Desktop.
Basic zones implementation
// 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