Skip to content

Instantly share code, notes, and snippets.

@devsnek
Created June 20, 2019 03:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save devsnek/07a61aec7d9f40d1495048790dd30a8f to your computer and use it in GitHub Desktop.
Save devsnek/07a61aec7d9f40d1495048790dd30a8f to your computer and use it in GitHub Desktop.
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
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
Copy link
Author

devsnek commented Jun 20, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment