README
Scope -
Use asynchronous hooks to implement scope functionality. Async hooks are not a stable feature, but they are used in many places and appear to be stable. However, you may encounter problems, so please refer to the documentation and issues.
Prerequisites
It requires a deep understanding of the event loop.
- https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
- https://developer.ibm.com/languages/node-js/tutorials/learn-nodejs-the-event-loop/
You don't have to use asynchronous hooks directly, but I recommend learning them as well.
- https://nodejs.org/api/async_hooks.html
- https://itnext.io/a-pragmatic-overview-of-async-hooks-api-in-node-js-e514b31460e9
Usage
Scopes are logical contexts that are completely isolated anytime, anywhere. Multiple scopes can be nested, and scopes are identified by name.
- The provided callback is executed in the microtask callback.
- You don't have to wait for the scope to run if necessary.
await Scope.run('a', async (a: Scope): Promise<void> => {
expect(Scope.get('a')).toBe(a);
expect(Scope.get()).toBe(a);
expect(a.parent).toBeNull();
expect(a.root).toBe(a);
await Scope.run('b', async (b: Scope): Promise<void> => {
expect(Scope.get('a')).toBe(a);
expect(Scope.get('b')).toBe(b);
expect(Scope.get()).toBe(b);
expect(b.parent).toBe(a);
expect(b.root).toBe(a);
await Scope.run('c', async (c: Scope): Promise<void> => {
expect(Scope.get('a')).toBe(a);
expect(Scope.get('b')).toBe(b);
expect(Scope.get('c')).toBe(c);
expect(Scope.get()).toBe(c);
expect(c.parent).toBe(b);
expect(c.root).toBe(a);
});
});
});
Null is returned if the scope does not exist or if no scope with the specified name can be found.
expect(Scope.get()).toBeNull();
await Scope.run('a', (): void => {
expect(Scope.get('b')).toBeNull();
});
Scope Destruction
Scopes are implemented in a tree structure, and are sequentially destroyed from child nodes to parent nodes. When the garbage collector destroys an asynchronous resource associated with a node, the node is not destroyed immediately, it is marked as destroyable.
So when is the scope actually being destroyed?
Destroyed only if no child nodes exist or all are destroyed, and destroys the parent node recursively if the parent node can be destroyed. Therefore, be careful as creating an infinite number of asynchronous resources within the scope can increase memory usage.
How do you know that the scope has been destroyed?
Use a callback that gets called when the scope is destroyed. Don't create scopes inside callbacks. Fatal problems can occur.
await Scope.run(name, (scope: Scope): void => {
scopedValues.set(scope.id, value);
scope.onDestroy((scope: Scope): void => {
scopedValues.delete(scope.id);
});
});