异步 Provider
本教程共 47 篇 · 第 9 篇 · 更新于 2026-08-09 · 约 6 分钟阅读
本节目标:掌握异步 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 {}
关键点:useFactory 是 async 函数,返回一个 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?
两种方式的区别:
| 对比项 | 异步 Provider | onModuleInit |
|---|---|---|
| 时机 | 应用启动前,阻塞启动 | 模块初始化后,不阻塞启动 |
| 返回值 | 直接作为 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。