Skip to content

Instantly share code, notes, and snippets.

@bmeck

bmeck/zomains.js Secret

Last active April 27, 2016 16:17
Show Gist options
  • Save bmeck/05957f8721e9f41039fbb0f321fe943a to your computer and use it in GitHub Desktop.
Save bmeck/05957f8721e9f41039fbb0f321fe943a to your computer and use it in GitHub Desktop.
for @trevnorris Zones implemented using domains for testing
"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