---
url: https://masyaka.github.io/injecute/guide/async.md
description: >-
  Services created asynchronously, in a DIContainer with defer() or in an
  AsyncDIContainer that awaits every dependency.
---

# Async services

There are two ways to work with services that are created asynchronously (connections, loaded config):

* **A few async services in a `DIContainer`**: factories may return promises, and `defer()` awaits them
  for the services that depend on them.
* **An `AsyncDIContainer`**, when much of the graph is async: every dependency is awaited before a
  factory runs, and every `get()` returns a promise.

## Async factories in a `DIContainer`

A factory can return a promise. A singleton caches that promise, so concurrent `get()` calls share one
creation. If the promise rejects, the container drops it and the next `get()` tries again; the rejection
is wrapped in [`INJECUTE_RESOLUTION_FAILED`](../errors/resolution-failed.md) with the resolution path.
`dispose()` releases the resolved value.

Services that depend on an async service receive the promise. `defer()` wraps a factory so its promised
arguments are awaited first:

```ts
const app = new DIContainer()
  .addSingleton('config', async (): Promise<Config> => ({
    dbUrl: 'postgres://localhost',
  }))
  // defer(): awaits the promised arguments, so the factory receives the resolved config
  .addSingleton(
    'db',
    defer((config: Config) => new Database(config.dbUrl)),
    ['config'],
  );

export async function connect(): Promise<Database> {
  return app.get('db'); // Promise<Database>
}
```

`get()` of a deferred service returns a promise; await it where you use it, or resolve the async
services once at startup:

```ts
const [db, cache] = await Promise.all([app.get('db'), app.get('cache')]);
```

## `AsyncDIContainer`

An `AsyncDIContainer` awaits every dependency before it calls a factory, so factories and classes receive
resolved values, without `defer()`. Its service types are the resolved types: a factory returning
`Promise<Config>` registers a `Config`. Every resolution returns a promise:

```ts
const app = new AsyncDIContainer()
  .addSingleton('config', loadConfig) // Promise<Config> → registers Config
  .addSingleton('pool', (config) => connect(config.dbUrl), ['config']) // receives a Config
  .addSingleton('users', UserRepository, ['pool']); // receives a Pool

const users = await app.get('users'); // UserRepository
```

It has the same API as `DIContainer`, with these differences:

* `get()`, `call()`, `injecute()`, `bind()` functions and `createResolver()` functions return promises.
  A missing key rejects instead of throwing.
* Singletons behave like async singletons in a `DIContainer`: created once under concurrent `get()`
  calls, retried after a failure, disposed once resolved.
* Forks of an async container are async too, including `fork({ isolated: true })`.

### Modules and consumers

A module for an async container takes an `AsyncServiceRegistry`, and code that only resolves services
takes an `AsyncServiceProvider`. They mirror [`ServiceRegistry` and `ServiceProvider`](./containers.md),
with promises from `get()`:

```ts
// A module for an async container takes an AsyncServiceRegistry
const addReports = (c: AsyncServiceRegistry<{ users: UserRepository }>) =>
  c.addSingleton('reports', (users) => ({ weekly: () => users.findAll() }), [
    'users',
  ]);

// Code that only resolves services takes an AsyncServiceProvider
function createHandler(
  services: AsyncServiceProvider<{
    reports: { weekly: () => Promise<string> };
  }>,
) {
  return async () => (await services.get('reports')).weekly();
}

const withReports = app.extend(addReports);
const handler = createHandler(withReports);
```

Sync and async types don't mix: a module typed `ServiceRegistry` can't be applied to an async container,
and an async container can't be passed where a `ServiceProvider` is expected, because their `get()`
returns values, not promises.

### Tests

Override services in an isolated fork, as with a `DIContainer` ([Testing](./testing.md)). A replacement
can be a value or a promise:

```ts
const test = app
  .fork({ isolated: true })
  .addInstance('config', { dbUrl: 'postgres://test' }, { replace: true });
const testUsers = await test.get('users'); // built with the test config; app is untouched
```

### Startup and shutdown

`preload()` returns a promise that settles once every service is created; await it at startup. To run
work at startup (migrations, consumers, listening) and stop it in reverse, use
[lifecycle hooks](./startup-shutdown.md): `startLifecycle()` awaits each hook, including in an async container.
`dispose()` waits for singletons that are still being created, then releases them:

```ts
async function start() {
  await preload(withReports); // creates every service now, so wiring errors surface at boot
}

async function shutdown() {
  await test.dispose();
  await app.dispose(); // closes the pool, once it is resolved
}
```

### Middlewares, events and utilities

* A middleware's `next()` returns a promise for services a factory creates (instances added with
  `addInstance()` pass through as they are). A middleware that measures or wraps values should await it:

  ```ts
  app.use(async (key, next) => {
    const start = performance.now();
    const value = await next();
    console.debug(`${String(key)}: ${performance.now() - start}ms`);
    return value;
  });
  ```

* The `value` of `get` and `produce` events may be a promise.

* `preload()` and `buildServicesGraph()` accept an async container. `createProxyAccessor()`,
  `createNamedResolvers()`, `createResolversTuple()` and `setCacheInstance()` are for `DIContainer`; use
  `createResolver()` and isolated forks instead.

## Which one to use

| Situation                                                    | Use                                            |
| ------------------------------------------------------------ | ---------------------------------------------- |
| Mostly sync services, one or two async ones (config, a pool) | `DIContainer` + `defer()`, or resolve at start |
| Much of the graph depends on async services                  | `AsyncDIContainer`                             |
| Code that must call `get()` synchronously                    | `DIContainer`                                  |
