首页 / NestJS 入门教程 / 异步 Provider

NestJS 入门教程

异步 Provider

本教程共 47 篇 · 第 9 篇 · 更新于 2026-08-09 · 约 6 分钟阅读

NestJS异步ProvideruseFactoryPromise延迟初始化数据库连接

本节目标:掌握异步 Provider 的写法和用途,学会在应用启动前等待异步资源就绪。

为什么需要异步 Provider

有些资源在创建时需要异步操作。比如连接数据库、读取远程配置、初始化缓存。

普通的 Provider 是同步创建的——new 一个类就完事了。但如果创建过程本身是异步的呢?

// 数据库连接是异步的
const connection = await createConnection({
  host: 'localhost',
  database: 'mydb',
});

你没法在构造函数里 await。这时候就需要异步 Provider。

基本写法

异步 Provider 用 useFactory 返回一个 Promise:

@Module({
  providers: [
    {
      provide: 'DATABASE_CONNECTION',
      useFactory: async () => {
        const connection = await createConnection({
          host: 'localhost',
          user: 'root',
          database: 'mydb',
        });
        return connection;
      },
    },
  ],
})
export class AppModule {}

关键点:useFactoryasync 函数,返回一个 Promise。NestJS 会等这个 Promise resolve 之后,才把结果注入到依赖它的地方。

也就是说——应用启动会等到异步 Provider 完成。数据库没连上,应用就不会开始接收请求。

Note

这正好是你要的行为。如果数据库还没连上就开始接收请求,所有请求都会报错。异步 Provider 帮你避免了这个问题。

注入异步 Provider

和其他自定义 Provider 一样,用 @Inject() 加 token 来注入:

@Injectable()
export class UsersService {
  constructor(
    @Inject('DATABASE_CONNECTION')
    private connection: Connection,
  ) {}

  async findAll() {
    const [rows] = await this.connection.execute('SELECT * FROM users');
    return rows;
  }
}

带依赖的异步 Provider

异步工厂函数也可以注入其他 Provider:

@Module({
  providers: [
    ConfigService,
    {
      provide: 'DATABASE_CONNECTION',
      useFactory: async (configService: ConfigService) => {
        const connection = await createConnection({
          host: configService.get('DB_HOST'),
          port: configService.get('DB_PORT'),
          database: configService.get('DB_NAME'),
        });
        return connection;
      },
      inject: [ConfigService],
    },
  ],
})
export class AppModule {}

inject 数组声明工厂函数需要的依赖。NestJS 会先创建 ConfigService,再把它传给工厂函数。

实际场景

场景一:数据库连接

最常见的用途。应用启动前必须连上数据库:

{
  provide: 'DATABASE_CONNECTION',
  useFactory: async () => {
    const connection = await createConnection({
      type: 'mysql',
      host: process.env.DB_HOST,
      port: parseInt(process.env.DB_PORT, 10),
      username: process.env.DB_USER,
      password: process.env.DB_PASS,
      database: process.env.DB_NAME,
    });
    return connection;
  },
}

场景二:远程配置

从远程服务加载配置:

{
  provide: 'REMOTE_CONFIG',
  useFactory: async () => {
    const response = await fetch('https://config-service/api/config');
    const config = await response.json();
    return config;
  },
}

场景三:Redis 连接

{
  provide: 'REDIS_CLIENT',
  useFactory: async () => {
    const client = createClient({
      url: process.env.REDIS_URL,
    });
    await client.connect();
    return client;
  },
}

异步 Provider vs onModuleInit

你可能会问:我也可以在 Service 的 onModuleInit() 里做异步初始化啊,为什么还要用异步 Provider?

两种方式的区别:

对比项异步 ProvideronModuleInit
时机应用启动前,阻塞启动模块初始化后,不阻塞启动
返回值直接作为 Provider 的值在已有实例上执行初始化
适用场景创建外部资源(连接、客户端)已有实例后的初始化逻辑

简单来说:

  • 需要创建一个异步资源,且希望应用启动前就绑——用异步 Provider
  • 已经有一个实例,需要在初始化后做一些准备工作——用 onModuleInit
// 方式一:异步 Provider
{
  provide: 'REDIS_CLIENT',
  useFactory: async () => {
    const client = createClient({ url: 'redis://localhost:6379' });
    await client.connect();
    return client;
  },
}

// 方式二:onModuleInit
@Injectable()
export class RedisService implements OnModuleInit {
  private client: RedisClientType;

  async onModuleInit() {
    this.client = createClient({ url: 'redis://localhost:6379' });
    await this.client.connect();
  }
}
Tip

两种方式都能工作。异步 Provider 更”纯粹”——创建完直接可用。onModuleInit 更灵活——可以在初始化时访问其他注入的依赖。

错误处理

异步 Provider 如果失败了(比如数据库连不上),应用启动会直接失败。这是期望的行为——连不上数据库就不该启动。

但你可以加错误处理来提供更友好的报错信息:

{
  provide: 'DATABASE_CONNECTION',
  useFactory: async () => {
    try {
      const connection = await createConnection(options);
      return connection;
    } catch (error) {
      console.error('数据库连接失败:', error.message);
      throw new Error('无法连接数据库,请检查配置');
    }
  },
}
Note

生产环境中,你可能还需要加重试逻辑。数据库偶尔会重启,直接失败可能太激进。可以用 async-retry 之类的库做几次重试。

小结

异步 Provider 解决了一个很具体的问题:在应用启动前,等待异步资源就绪。

核心就一句话:useFactory 返回 Promise,NestJS 会等它 resolve。

典型场景:数据库连接、Redis 连接、远程配置加载。

写法和普通自定义 Provider 一样,只是工厂函数加了 async