-
-
Save bmeck/05957f8721e9f41039fbb0f321fe943a to your computer and use it in GitHub Desktop.
for @trevnorris Zones implemented using domains for testing
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
"use strict"; | |
const domain = require('domain'); | |
function HostZoneSetup(zone, opts) { | |
// setup by host: node, browser, etc. | |
// generally populate [[HostDefined]] | |
} | |
// Required for special cases like Domains | |
let GUARDING = false; | |
let CALL_MAP = new WeakMap(); | |
function Call(zone, callback, thisArg, argumentsList, guarded) { | |
if (CALL_MAP.has(zone)) { | |
return CALL_MAP.get(zone).CallInZone(zone, callback, thisArg, argumentsList, guarded); | |
} | |
throw TypeError(`Expected Zone ${zone} to have Call`); | |
} | |
function HandleError(zone, e) { | |
let handled = false; | |
while (handled !== true && zone !== null) { | |
const meta = CALL_MAP.get(zone); | |
if (typeof meta.handleError === 'function') { | |
handled = meta.handleError(e); | |
} | |
zone = zone.parent; | |
} | |
if (handled !== true) { | |
throw e; | |
} | |
} | |
function CallInZone(zone, callback, thisArg, argumentsList, guarded) { | |
const tmp = CURRENT_ZONE; | |
CURRENT_ZONE = zone; | |
const tmp_guarding = GUARDING; | |
GUARDING = guarded; | |
const meta = CALL_MAP.get(zone); | |
const domain = meta.domain; | |
try { | |
let ret; | |
if (!guarded) { | |
domain.run( | |
() => { | |
ret = callback.apply(thisArg, argumentsList) | |
} | |
) | |
} | |
else { | |
try { | |
return Call(zone, callback, undefined, []); | |
} | |
catch (e) { | |
HandleError(zone, e); | |
return; | |
} | |
} | |
return ret; | |
} | |
finally { | |
CURRENT_ZONE = tmp; | |
GUARDING = tmp_guarding; | |
} | |
} | |
// punch promise to emulate behavior | |
const $then = Promise.prototype.then; | |
Promise.prototype.then = function (on_fulfill, on_reject) { | |
const zone = Zone.current; | |
const guarded = GUARDING; | |
return $then.call(this, | |
function () { | |
return Call(zone, on_fulfill, this, arguments, guarded); | |
}, | |
function () { | |
return Call(zone, on_reject, this, arguments, guarded); | |
} | |
); | |
} | |
Promise.prototype.catch = function (on_reject) { | |
return this.then(undefined, on_reject); | |
} | |
// where we will store [[ParentZone]] | |
const ParentZoneMap = new WeakMap(); | |
// where we will store [[HostDefined]] | |
const HostDefinedZoneMap = new WeakMap(); | |
// Zone constructor is %Zone% | |
// Zone.prototype is %ZonePrototype% | |
class Zone extends Object { | |
static get current() { | |
return CURRENT_ZONE; | |
} | |
// { "name": String? , "parent": Zone | null } | |
constructor(options) { | |
super(); | |
options = typeof options === 'undefined' ? {} : options; | |
let name = '(unnamed zone)'; | |
let parent = null; | |
Object(options); // require it to be coercible | |
const opt_name = options.name; | |
if (opt_name !== undefined) name = opt_name; | |
const opt_parent = options.parent; | |
if (opt_parent !== undefined) parent = opt_parent; | |
if (parent !== null) { | |
if (ParentZoneMap.has(parent) !== true) { | |
throw TypeError(); | |
} | |
} | |
ParentZoneMap.set(this, parent); | |
Object.defineProperty(this, 'name', { | |
value: name, | |
writable: false, | |
enumerable: false, //? | |
cofigurable: true | |
}); | |
HostZoneSetup(this, options); | |
// VM: optimize to per class, not per instance | |
const meta = { | |
CallInZone, | |
domain: domain.create() | |
}; | |
const self = this; | |
const $enter = meta.domain.enter; | |
const $exit = meta.domain.exit; | |
let last_zone; | |
// rage against the domain | |
meta.domain._events = Object.freeze({ | |
error: Object.freeze([ | |
function (e) { | |
HandleError(self, e); | |
} | |
]) | |
}) | |
delete meta.domain.addListener; | |
delete meta.domain.removeListener; | |
delete meta.domain.on; | |
delete meta.domain.removeAllListeners; | |
meta.domain.enter = function enter() { | |
last_zone = CURRENT_ZONE; | |
CURRENT_ZONE = self; | |
return $enter.apply(this, arguments); | |
} | |
meta.domain.exit = function exit() { | |
CURRENT_ZONE = last_zone; | |
return $exit.apply(this, arguments); | |
} | |
meta.domain.zone = this; | |
meta.handleError = options.handleError; | |
Object.freeze(meta.domain); | |
CALL_MAP.set(this, meta); | |
} | |
get parent() { | |
return ParentZoneMap.get(this); | |
} | |
fork(options) { | |
options = typeof options === 'undefined' ? | |
{ } | |
: options; | |
let name = typeof options.name === 'undefined' ? | |
(''+this.name) + " child" : | |
options.name; | |
let handleError = typeof options.handleError === 'function' ? | |
options.handleError : | |
undefined; | |
return new (this.constructor)({ | |
name, | |
parent: this, | |
handleError | |
}); | |
} | |
wrap(callback) { | |
const zone = this; | |
return function() { | |
return Call(zone, callback, undefined, arguments); | |
} | |
} | |
wrapGuarded(callback) { | |
return function() { | |
return Call(this, callback, undefined, arguments, true); | |
} | |
} | |
run(callback) { | |
const zone = this; | |
return Call(zone, callback, undefined, []); | |
} | |
runGuarded(callback) { | |
const zone = this; | |
return Call(zone, callback, undefined, [], true); | |
} | |
} | |
Object.defineProperty(Zone, 'prototype', { | |
value: Zone.prototype, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
// this is [[CurrentZone]] | |
let CURRENT_ZONE = new Zone({name:'(root zone)'}); | |
// EXAMPLE | |
const root_zone = Zone.current | |
let escaped; | |
try { | |
Zone.current.fork().run(()=>{ | |
if (Zone.current === root_zone) { | |
throw Error('zone.fork.run should change zone'); | |
} | |
else { | |
console.log('fork.run changed zone ok') | |
} | |
if (Zone.current.parent !== root_zone) { | |
throw Error('zone.fork.run should refer to parent'); | |
} | |
else { | |
console.log('fork.run refers to parent ok') | |
} | |
Promise.resolve('test').then(() => { | |
if (Zone.current === root_zone) { | |
throw Error('promise.then should preserve zone'); | |
} | |
else { | |
console.log('promise.then preserves zone ok') | |
} | |
}); | |
/*setTimeout(() => { | |
if (Zone.current !== root_zone) { | |
throw Error('non promise queueing should not preserve zone'); | |
} | |
else { | |
console.log('non promise queueing restored zone ok'); | |
} | |
});*/ | |
escaped = () => { | |
if (Zone.current !== root_zone) { | |
throw Error('function references should not preserve zone'); | |
} | |
else { | |
console.log('function reference did not carry zone, ok'); | |
} | |
} | |
throw 'test'; | |
}); | |
} | |
catch (e) { | |
if (e !== 'test') { | |
throw e; | |
} | |
if (Zone.current !== root_zone) { | |
throw Error('zone.run should reset zone even if abrupt completion occurs'); | |
} | |
else { | |
console.log('zone.run handles throw ok') | |
} | |
} | |
escaped(); | |
// for @trevnorris | |
// modify "root zone" | |
// child | |
let child = Zone.current.fork({ | |
name: 'child_domain', | |
handleError: function (e) { | |
console.log(e); | |
return true; | |
} | |
}); | |
console.log(Zone.current.name); | |
child.runGuarded(()=> { | |
require('fs').readFile(__filename, function () { | |
console.log('preserves Zone using domains', Zone.current.name) | |
throw 1; | |
}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment