@typemon/scope

Scope for TypeScript

Usage no npm install needed!

<script type="module">
  import typemonScope from 'https://cdn.skypack.dev/@typemon/scope';
</script>

README

Scope - version license typescript-version

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.

You don't have to use asynchronous hooks directly, but I recommend learning them as well.

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);
    });
});