Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
import EventEmitter from 'events';
import {
AsyncResource,
executionAsyncId,
createHook,
} from 'async_hooks';
const zones = new Map();
export default class Zone extends EventEmitter {
#name;
#resource;
static get current() {
return zones.get(executionAsyncId());
}
constructor({ name = null } = {}) {
super();
this.#name = name;
this.#resource = new AsyncResource('ZONE', {
triggerAsyncId: executionAsyncId(),
requireManualDestroy: false,
});
zones.set(this.#resource.asyncId(), this);
}
run(fn, ...args) {
let r;
try {
r = this.#resource.runInAsyncScope(fn, undefined, ...args);
if (typeof r.then === 'function') {
r.then(undefined, (err) => {
this.emit('error', err);
});
}
} catch (err) {
this.emit('error', err);
}
return r;
}
fork({ name } = {}) {
const z = new Zone({ name });
return z;
}
get name() {
return this.#name;
}
get parent() {
return zones.get(this.#resource.triggerAsyncId());
}
}
zones.set(executionAsyncId(), new Zone());
createHook({
init: (asyncId, type, triggerAsyncId) => {
zones.set(asyncId, zones.get(triggerAsyncId));
},
destroy: (asyncId) => {
zones.delete(asyncId);
},
}).enable();
process.on('multipleResolves', (type, promise, value) => {
Zone.current.emit('multipleResolves', type, promise, value);
});
process.on('unhandledRejection', (reason, promise) => {
Zone.current.emit('unhandledRejection', reason, promise);
});
process.on('rejectionHandled', (promise) => {
Zone.current.emit('rejectionHandled', promise);
});
@Jamesernator

This comment has been minimized.

Copy link

Jamesernator commented Jun 20, 2019

Currently child zones don't work with unhandledRejection/rejectionHandled e.g. this never prints anything:

async function main() {
  const unhandled = Promise.reject(12)
  const eventuallyHandled = new Promise(resolve => setTimeout(resolve, 1000))
  
  await new Promise(resolve => setTimeout(resolve, 2000))
  
  try {
    await eventuallyHandled
  } catch (err) {
    // caught
  }
}

const mainZone = Zone.current.fork({ name: 'main' })

mainZone.on('unhandledRejection', () => console.log("An unhandledrejection occured!"))
mainZone.on('rejectionHandled', () => console.log("A rejection was handled!")

mainZone.run(main)

This happens because process.on('unhandledRejection' | 'rejectionHandled' is always run in executionAsyncId() === 0 which means Zone.current is never the mainZone in those callbacks.

I'm not sure how to repair it either, all that is received inside process.on('unhandledRejection' is the promise (and its reason) which can't be used to extract the asyncId of the PromiseWrap as far as I can tell.

@devsnek

This comment has been minimized.

Copy link
Owner Author

devsnek commented Jun 20, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.