异步提供者 (Async Providers)
让应用“耐心等待”:NestJS 异步提供者详解
想象一个最常见的场景:你的应用需要连接数据库。
这个连接过程不是瞬间完成的。它需要和数据库服务器进行网络通信、验证身份、建立会话……这可能需要几十毫秒甚至几秒钟。在连接成功之前,任何试图使用数据库的服务(比如 UsersService
)都会报错。
我们绝不希望在应用还没准备好数据库连接时,就开始接收和处理用户的请求。我们希望整个应用能“暂停”,耐心等待数据库连接成功后,再说:“好了,我准备好了,放马过来吧!”
异步提供者就是 NestJS 提供的,用来优雅地解决这个问题的官方方案。
1. 异步的基石:useFactory
的华丽变身
还记得我们上一篇讲到的最强配方 useFactory
吗?它允许我们用一个工厂函数来创建服务。
const myServiceProvider = {
provide: 'MY_SERVICE',
useFactory: () => {
// 这个函数返回的值就是被注入的值
return 'a simple value';
},
};
异步提供者的魔法就在于:如果这个 useFactory
函数是一个 async
函数(也就是说,它返回一个 Promise
),那么 NestJS 的依赖注入系统会智能地等待这个 Promise
完成 (resolve),然后将 Promise
解析出的结果作为最终的注入值。
更关键的是,NestJS 会暂停整个应用的启动流程,直到所有这样的异步提供者都准备就绪为止。这正是我们想要的效果!
2. 实战演练:创建一个异步的数据库连接
让我们通过一个完整的例子来感受一下。
第一步:模拟一个耗时的数据库连接库
为了方便演示,我们不真的去连数据库,而是创建一个 fake-db-lib.ts
文件,用 setTimeout
来模拟连接耗时。
src/db/fake-db-lib.ts
export interface DbConnection {
query: (sql: string) => Promise<any>;
isConnected: boolean;
}
export function createDbConnection(options: any): Promise<DbConnection> {
// 返回一个 Promise
return new Promise(resolve => {
console.log(`[数据库] 正在尝试连接到 ${options.host}... ⏳`);
// 模拟 2 秒的网络延迟
setTimeout(() => {
console.log('[数据库] 连接成功!✅');
// Promise 解析出的值是一个模拟的连接对象
const connection: DbConnection = {
query: async (sql) => {
console.log(`[数据库] 正在执行查询: ${sql}`);
return [{ id: 1, name: 'John Doe' }];
},
isConnected: true,
};
resolve(connection);
}, 2000);
});
}
第二步:创建异步提供者
现在,我们使用 async useFactory
来创建我们的数据库连接提供者。
src/db/database.providers.ts
import { createDbConnection, DbConnection } from './fake-db-lib';
export const DATABASE_CONNECTION = 'DATABASE_CONNECTION';
export const databaseProviders = [
{
provide: DATABASE_CONNECTION,
// 看这里!工厂函数变成了 async
useFactory: async (): Promise<DbConnection> => {
// 我们可以 await 这个耗时的操作
const connection = await createDbConnection({ host: 'localhost' });
// 返回 Promise 解析后的结果
return connection;
},
},
];
第三步:在模块中注册并导出
我们将这个提供者放入一个 DatabaseModule
中,并导出它,以便其他模块可以使用。
src/db/database.module.ts
import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';
@Module({
providers: [...databaseProviders],
exports: [...databaseProviders], // 必须导出才能在其他模块注入
})
export class DatabaseModule {}
第四步:在服务中使用这个连接
现在,我们创建一个 UsersService
,它需要注入 DATABASE_CONNECTION
。
src/users/users.service.ts
import { Injectable, Inject, OnModuleInit } from '@nestjs/common';
import { DATABASE_CONNECTION } from '../db/database.providers';
import { DbConnection } from '../db/fake-db-lib';
@Injectable()
export class UsersService implements OnModuleInit {
constructor(
@Inject(DATABASE_CONNECTION) private connection: DbConnection,
) {}
// OnModuleInit 是一个生命周期钩子,当模块初始化完成后被调用
onModuleInit() {
console.log(`[UsersService] 服务已初始化,数据库连接状态: ${this.connection.isConnected}`);
}
async findOne(id: number): Promise<any> {
// 当我们调用这个方法时,可以 100% 确定 connection 已经准备好了
const users = await this.connection.query(`SELECT * FROM users WHERE id = ${id}`);
return users[0];
}
}
最后,启动应用,观察控制台!
你需要将 DatabaseModule
和 UsersModule
导入到你的 AppModule
中。当你运行 npm run start:dev
时,你会看到这样的输出:
# 应用启动
[数据库] 正在尝试连接到 localhost... ⏳
# ... (等待 2 秒) ...
[数据库] 连接成功!✅
[UsersService] 服务已初始化,数据库连接状态: true
[Nest] 12345 - 07/16/2025, 5:00:02 PM LOG [NestApplication] Nest application successfully started ✨
# 在此之后,应用才真正开始监听端口和接收请求
这个输出清晰地证明了:NestJS 应用等待了我们的 useFactory
完成,然后才完成了 UsersService
的初始化,并最终宣布“应用启动成功”。
3. 异步工厂与依赖注入的结合
异步提供者最强大的地方在于,它的工厂函数本身也可以依赖注入其他服务,哪怕是普通的同步服务。
比如,我们的数据库连接信息可能存储在一个 ConfigService
中。
src/config/config.service.ts
@Injectable()
export class ConfigService {
getDatabaseOptions() {
console.log('[ConfigService] 提供数据库配置。');
return {
host: 'production.db.server.com',
user: 'prod_user',
};
}
}
现在,我们可以修改我们的 databaseProviders
,让它先注入 ConfigService
,再用获取到的配置去创建连接。
src/db/database.providers.ts
(增强版)
import { ConfigService } from '../config/config.service';
// ... 其他 import
export const databaseProviders = [
{
provide: DATABASE_CONNECTION,
// 1. 注入 ConfigService
inject: [ConfigService],
// 2. 工厂函数会接收到注入的服务作为参数
useFactory: async (configService: ConfigService): Promise<DbConnection> => {
const options = configService.getDatabaseOptions();
const connection = await createDbConnection(options);
return connection;
},
},
];
别忘了在 DatabaseModule
的 imports
中加入 ConfigModule
,并把 ConfigService
加入 ConfigModule
的 providers
和 exports
中,这样依赖注入才能找到它。
这个模式非常强大,它允许你将配置、连接逻辑、和其他服务完美地组合在一起,创建出初始化过程既健壮又清晰的服务。
总结
异步提供者不是一个全新的概念,它只是我们熟悉的 useFactory
的一个自然延伸。
- 核心思想:当你的服务初始化需要时间(如网络请求、文件读取、建立连接),就把它封装在一个
async
的useFactory
函数中。 - 最大好处:NestJS 会成为一个可靠的“工头”,它会确保所有这些“慢工出细活”的异步任务全部完成后,才让你的应用正式“开张营业”。这从根本上避免了因服务未就绪而导致的各种运行时错误。
当你需要与任何外部系统(数据库、缓存、消息队列、第三方 API)集成时,异步提供者都将是你代码库中保证稳定和可预测性的关键工具。