Skip to content

Commit

Permalink
Add waitAsync, adjust docs and readme
Browse files Browse the repository at this point in the history
  • Loading branch information
zheksoon committed Apr 4, 2024
1 parent cc7f5dd commit 4f4cdbf
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 55 deletions.
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ const car = container.inject(Car);
car.park();
```

Container scoped classes usually are [registered in the container](#class-registration) first. Without it, the class will "stick" to the container it's used in.
Container-scoped classes usually are [registered in the container](#class-registration) first. Without it, the class will "stick" to the container it's used in.

### Resolution scope

Expand Down Expand Up @@ -269,12 +269,12 @@ To unregister a class, use the `unregister` method:
container.unregister(FooBar);
```

After the unregistration, the class will be removed from the container and all its child containers, and the next injection will return a new instance.
After that, the class will be removed from the container and all its child containers, and the next injection will return a new instance.

## Injection with tokens

Instead of passing a class the `inject`, you can use tokens instead.
Tokens can be used for class, value, and factory injection.
Instead of passing a class to the `inject`, you can use **tokens** instead.
The token injection can be used for **class, value, and factory** injection.
Here's detailed information about each type.

### Class tokens
Expand Down Expand Up @@ -326,6 +326,7 @@ const wildAnimal = wild.inject(animalToken);
// Returns Cat instance
const zooAnimal = zoo.inject(animalToken);
```

</details>

The class token registration can also override the scope of the class:
Expand All @@ -341,8 +342,6 @@ Value tokens are useful to inject a constant value:
```typescript
import { Token } from "dioma";

const container = new Container();

const token = new Token<string>("Value token");

container.register({ token, value: "Value" });
Expand All @@ -360,8 +359,6 @@ The factory takes the current container as the first argument and returns a valu
```typescript
import { Token } from "dioma";

const container = new Container();

const token = new Token<string>("Factory token");

container.register({ token, factory: (container) => "Value" });
Expand Down Expand Up @@ -469,8 +466,8 @@ class B {
const a = await injectAsync(A);
const b = await injectAsync(B);

// All cycles are resolved on the next tick
await new Promise((resolve) => setTimeout(resolve, 0));
// Wait until all promises are resolved
await globalContainer.waitAsync();

a.doWork();
b.doAnotherWork();
Expand All @@ -480,9 +477,9 @@ b.doAnotherWork();

Async injection has an undefined behavior when there is a loop with transient dependencies. It may return an instance with an unexpected loop, or throw the `Circular dependency detected in async resolution` error, so it's better to avoid such cases.

As defined in the code above, you need to **wait for the next tick** to get all instance promises resolved, even if you use `await injectAsync(...)`.
As defined in the code above, you need to use `container.waitAsync()` or **wait for the next tick** to get all instance promises resolved, even if you use `await injectAsync(...)`.

Generally, if you expect your dependency to have an async resolution, it's better to inject it with `injectAsync`, as in the example above. But, you can also use `inject` for async injection as long as you wait for the next tick after it.
Generally, if you expect your dependency to have an async resolution, it's better to inject it with `injectAsync`, as in the example above. But, you can also use `inject` for async injection as long as you wait for it as above.

Tokens also can be used for async injection as well:

Expand Down Expand Up @@ -550,6 +547,10 @@ Injects the instance of the class or token, and provides arguments to the constr

Injects the promise of the instance of the class or token, and provides arguments to the constructor or factory function.

### `container.waitAsync()`

Returns a promise that resolves when all current async injections are resolved.

### `container.register({ class, token?, scope? })`

### `container.register({ token, value })`
Expand All @@ -560,7 +561,7 @@ Registers the class, value, or factory with the token in the container.

### `container.unregister(classOrToken)`

Unregisters the class or token from the container.
Unregister the class or token from the container.

### `container.childContainer(name?)`

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dioma",
"version": "0.4.0",
"version": "0.4.1",
"description": "Elegant dependency injection container for vanilla JavaScript and TypeScript",
"license": "MIT",
"repository": {
Expand Down
6 changes: 5 additions & 1 deletion src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class Container {
return new Container(this, name);
};

public __getInstance(cls: any, args: any[] = []) {
public $getInstance(cls: any, args: any[] = []) {
let instance = null;
let container: Container | null = this;

Expand Down Expand Up @@ -177,6 +177,10 @@ export class Container {
return promise;
};

waitAsync = async () => {
await Promise.all(this.pendingPromiseMap.values());
};

register<T extends Token<any>>(descriptor: TokenValueDescriptor<T>): void;

register<T extends Token<any>>(descriptor: TokenFactoryDescriptor<T>): void;
Expand Down
6 changes: 3 additions & 3 deletions src/scopes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class Scopes {
throw new ArgumentsError(SingletonScope.name, cls.name);
}

return globalContainer.__getInstance(cls);
return globalContainer.$getInstance(cls);
};
}

Expand All @@ -25,13 +25,13 @@ export class Scopes {
throw new ArgumentsError(ContainerScope.name, cls.name);
}

return container.__getInstance(cls);
return container.$getInstance(cls);
};
}

public static Resolution(): ScopeHandler {
return function ResolutionScope(cls, args, _, resolutionContainer) {
return resolutionContainer.__getInstance(cls, args);
return resolutionContainer.$getInstance(cls, args);
};
}

Expand Down
74 changes: 37 additions & 37 deletions test/dioma.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { beforeEach, describe, expect, it } from "vitest";
import {
ArgumentsError,
// AsyncDependencyCycleError,
Container,
DependencyCycleError,
ScopedClass,
Scopes,
inject,
Token,
globalContainer,
DependencyCycleError,
AsyncDependencyCycleError,
inject,
injectAsync,
ArgumentsError,
Token,
ScopedClass,
} from "../src";
import { describe, it, expect, beforeEach } from "vitest";
import { ScopeHandler } from "../src/types";

const delay = (ms: number = 0) => new Promise((resolve) => setTimeout(resolve, ms));

describe("Dioma", () => {
beforeEach(() => {
Expand Down Expand Up @@ -429,7 +426,7 @@ describe("Dioma", () => {

const instance = inject(CircularDependencyA);

await delay();
await globalContainer.waitAsync();

expect(instance).toBeInstanceOf(CircularDependencyA);
expect(instance.instanceB).toBeInstanceOf(CircularDependencyB);
Expand All @@ -438,7 +435,7 @@ describe("Dioma", () => {

const instance2 = inject(CircularDependencyB);

await delay();
await globalContainer.waitAsync();

expect(instance2).toBeInstanceOf(CircularDependencyB);
expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA);
Expand Down Expand Up @@ -473,7 +470,7 @@ describe("Dioma", () => {

const instance = inject(CircularDependencyA);

await delay();
await globalContainer.waitAsync();

expect(instance).toBeInstanceOf(CircularDependencyA);
expect(instance.instanceB).toBeInstanceOf(CircularDependencyB);
Expand All @@ -482,7 +479,7 @@ describe("Dioma", () => {

const instance2 = inject(CircularDependencyB);

await delay();
await globalContainer.waitAsync();

expect(instance2).toBeInstanceOf(CircularDependencyB);
expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA);
Expand Down Expand Up @@ -634,9 +631,7 @@ describe("Dioma", () => {

const instance = inject(CircularDependencyA);

await delay();
await delay();
await delay();
await globalContainer.waitAsync();

expect(instance).toBeInstanceOf(CircularDependencyA);
expect(instance.instanceB).toBeInstanceOf(CircularDependencyB);
Expand All @@ -645,8 +640,7 @@ describe("Dioma", () => {

const instance2 = inject(CircularDependencyB);

await delay();
await delay();
await globalContainer.waitAsync();

expect(instance2).toBeInstanceOf(CircularDependencyB);
expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA);
Expand Down Expand Up @@ -681,7 +675,7 @@ describe("Dioma", () => {

const instance = inject(CircularDependencyA);

await delay();
await globalContainer.waitAsync();

expect(instance).toBeInstanceOf(CircularDependencyA);
expect(instance.instanceB).toBeInstanceOf(CircularDependencyB);
Expand All @@ -690,7 +684,7 @@ describe("Dioma", () => {

const instance2 = inject(CircularDependencyB);

await delay();
await globalContainer.waitAsync();

expect(instance2).toBeInstanceOf(CircularDependencyB);
expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA);
Expand Down Expand Up @@ -719,7 +713,7 @@ describe("Dioma", () => {

const instance = inject(CircularDependencyA);

await delay();
await globalContainer.waitAsync();

expect(instance).toBeInstanceOf(CircularDependencyA);
expect(instance.instanceB).toBeInstanceOf(CircularDependencyB);
Expand All @@ -732,7 +726,7 @@ describe("Dioma", () => {

expect(instance).not.toBe(instance2);

await delay();
await globalContainer.waitAsync();

expect(instance2).toBeInstanceOf(CircularDependencyB);
expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA);
Expand Down Expand Up @@ -767,7 +761,7 @@ describe("Dioma", () => {

const instance = await injectAsync(CircularDependencyA);

await delay();
await globalContainer.waitAsync();

expect(instance).toBeInstanceOf(CircularDependencyA);
expect(instance.instanceB).toBeInstanceOf(CircularDependencyB);
Expand All @@ -782,7 +776,7 @@ describe("Dioma", () => {
);
});

it("should throw error when trying to inject transients with async only loop", async () => {
it("should have unexpected result when trying to inject transients with async only loop", async () => {
let errorA: Error | null = null;
let errorB: Error | null = null;

Expand All @@ -803,7 +797,7 @@ describe("Dioma", () => {
}

class CircularDependencyB {
declare instanceA: CircularDependencyA;
public instanceA: CircularDependencyA;

constructor(private promiseA = injectAsync(CircularDependencyA)) {
this.promiseA
Expand All @@ -820,10 +814,16 @@ describe("Dioma", () => {

const instance = await injectAsync(CircularDependencyA);

await delay();
await globalContainer.waitAsync();

expect(errorA).toBeInstanceOf(AsyncDependencyCycleError);
expect(errorA).toBe(null);
expect(errorB).toBe(null);

expect(instance).toBeInstanceOf(CircularDependencyA);
expect(instance.instanceB).toBeInstanceOf(CircularDependencyB);

expect(instance.instanceB.instanceA).not.toBe(instance);
expect(instance.instanceB).not.toBe(instance.instanceB.instanceA.instanceB);
});

it("should be able to inject async for container scope", async () => {
Expand All @@ -849,15 +849,15 @@ describe("Dioma", () => {

const instance = container.inject(CircularDependencyA);

await delay();
await container.waitAsync();

expect(instance).toBeInstanceOf(CircularDependencyA);
expect(instance.instanceB).toBeInstanceOf(CircularDependencyB);
expect(instance.instanceB.instanceA).toBe(instance);

const instance2 = container.inject(CircularDependencyB);

await delay();
await container.waitAsync();

expect(instance2).toBeInstanceOf(CircularDependencyB);
expect(instance2.instanceA).toBeInstanceOf(CircularDependencyA);
Expand All @@ -879,7 +879,7 @@ describe("Dioma", () => {

const instance = await injectAsync(CircularDependencyA);

await delay();
await globalContainer.waitAsync();

expect(instance).toBeInstanceOf(CircularDependencyA);
expect(instance.instanceA).toBe(instance);
Expand Down Expand Up @@ -1017,7 +1017,7 @@ describe("Dioma", () => {

const instanceB = inject(ClassB, "test");

await delay();
await globalContainer.waitAsync();

expect(instanceB).toBeInstanceOf(ClassB);
expect(instanceB.value).toBe("test");
Expand Down Expand Up @@ -1227,7 +1227,7 @@ describe("Dioma", () => {

expect(instance).toBeInstanceOf(A);

await delay();
await container.waitAsync();

expect(instance.instanceB).toBeInstanceOf(B);
expect(instance.instanceB.instanceA).toBeInstanceOf(A);
Expand All @@ -1237,7 +1237,7 @@ describe("Dioma", () => {

expect(instance2).toBeInstanceOf(B);

await delay();
await container.waitAsync();

expect(instance2.instanceA).toBeInstanceOf(A);
expect(instance2.instanceA.instanceB).toBeInstanceOf(B);
Expand Down Expand Up @@ -1273,7 +1273,7 @@ describe("Dioma", () => {

expect(instance).toBeInstanceOf(A);

await delay();
await container.waitAsync();

expect(instance.instanceB).toBeInstanceOf(B);
expect(instance.instanceB.instanceA).toBeInstanceOf(A);
Expand All @@ -1283,7 +1283,7 @@ describe("Dioma", () => {

expect(instance2).toBeInstanceOf(B);

await delay();
await container.waitAsync();

expect(instance2.instanceA).toBeInstanceOf(A);
expect(instance2.instanceA.instanceB).toBeInstanceOf(B);
Expand Down Expand Up @@ -1319,7 +1319,7 @@ describe("Dioma", () => {

expect(instance).toBeInstanceOf(A);

await delay();
await container.waitAsync();

expect(instance.instanceB).toBeInstanceOf(B);
expect(instance.instanceB.instanceA).toBeInstanceOf(A);
Expand Down

0 comments on commit 4f4cdbf

Please sign in to comment.