Circular dependencies
When two values depend on each other, neither can be built first. Haywire refuses to build them, and container.check() reports the cycle.
Circular dependencies are best avoided, but when one can't be, break it by deferring one side. Mark the dependency with .deferred(), which changes its type from T to Promise<T>. The dependent is built first, and receives a promise that resolves once the other side is built:
import { bind, createContainer, createModule, identifier } from 'haywire';
class Chicken {
constructor(public readonly egg: Egg) {}
}
class Egg {
chicken?: Chicken;
constructor(chicken: Promise<Chicken>) {
void chicken.then(resolved => {
this.chicken = resolved;
});
}
}
const container = createContainer(
createModule(bind(Chicken).withDependencies([Egg]).withConstructorProvider()).addBinding(
bind(Egg).withDependencies([identifier(Chicken).deferred()]).withConstructorProvider()
)
);
const chicken = container.get(Chicken);Deferred ids work in synchronous containers too. Internally, the container does roughly this:
const getChicken = () => {
const { promise, resolve } = Promise.withResolvers<Chicken>();
// Chicken hasn't started building when Egg is built
const egg = new Egg(promise);
const chicken = new Chicken(egg);
resolve(chicken);
return chicken;
};The promise resolves before the request returns. Callbacks chained on it with .then() may not have run yet, so don't rely on them having finished.
A deferred id can also be a supplier: a promise that resolves to the supplier.
Never wait for a deferred dependency in its provider
The promise resolves only after the provider returns, so a provider that awaits it never finishes. Haywire can't detect this.
bind(Egg)
.withDependencies([identifier(Chicken).deferred()])
.withAsyncProvider(async chicken => new Egg(await chicken)); // Never resolvesNot checked by types
Types can't detect a cycle. container.check() can, so call it in a test.