一句话:Nest 9 起应通过
@nestjs/axios的HttpService发 HTTP 请求,返回的是 Observable,需subscribe或转 Promise 才能拿到数据。
写在前面
把博客后端从 Nest 8 升级到 Nest 9 后,直接在 Service 里用 axios.get() 会报错(如 axios.get//undefined)。Nest 官方推荐注入 HttpService,它基于 Axios + RxJS,返回 冷 Observable 而非 Promise。
如果你习惯 async/await,有两种路:firstValueFrom 转 Promise(推荐),或本文记录的 手动 new Promise + subscribe(历史写法,仍可用)。
核心内容
模块注册
typescript
// app.module.ts
import { HttpModule } from '@nestjs/axios'
@Module({
imports: [HttpModule],
// ...
})
export class AppModule {}
基本 GET 请求
typescript
import { HttpService } from '@nestjs/axios'
import { Injectable } from '@nestjs/common'
import { map } from 'rxjs/operators'
import { firstValueFrom } from 'rxjs'
@Injectable()
export class ResourcesService {
constructor(private readonly httpService: HttpService) {}
// 方式一:firstValueFrom(推荐)
async getBingImages(n = '1') {
const { data } = await firstValueFrom(
this.httpService.get(
`http://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=${n}`,
),
)
return data
}
// 方式二:pipe + map,仍返回 Observable
getImgObservable(n = '1') {
return this.httpService
.get(`http://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=${n}`)
.pipe(map((res) => res.data))
// 直接 console.log(res) 打印的是 Observable,需 subscribe 才有值
}
}
嵌套请求:Token 刷新 + 重试
以百度统计 Open API 为例:access_token 过期时先刷新,再重新拉数据。
typescript
import { HttpException, Injectable } from '@nestjs/common'
import { HttpService } from '@nestjs/axios'
import { catchError, map } from 'rxjs/operators'
@Injectable()
export class ResourcesService {
constructor(private readonly httpService: HttpService) {}
refresh_token = ''
access_token = ''
async baiDuTongJi(query: Record<string, string>) {
let data = await this.getBaiDuTongJiData(query)
if (data.error_code === 110 || data.error_code === 111) {
await this.refreshAccessToken()
data = await this.getBaiDuTongJiData(query)
}
return data
}
refreshAccessToken(): Promise<unknown> {
return new Promise((resolve, reject) => {
this.httpService
.get('http://openapi.baidu.com/oauth/2.0/token', {
params: {
grant_type: 'refresh_token',
refresh_token: this.refresh_token,
client_id: process.env.BAIDU_API_KEY,
client_secret: process.env.BAIDU_SECRET_KEY,
},
})
.pipe(
map((res) => res.data),
catchError(() => {
throw new HttpException('刷新 access_token 错误', 400)
}),
)
.subscribe({
next: (data) => {
this.access_token = data.access_token
this.refresh_token = data.refresh_token
resolve(data)
},
error: reject,
})
})
}
getBaiDuTongJiData(query: Record<string, string>): Promise<unknown> {
const { url, ...otherParams } = query
return new Promise((resolve, reject) => {
this.httpService
.get(`https://openapi.baidu.com${url}`, {
params: { ...otherParams, access_token: this.access_token },
})
.pipe(map((res) => res.data))
.subscribe({ next: resolve, error: reject })
})
}
}
用 RxJS 链式处理(mergeMap)
简单串行请求可用 mergeMap / switchMap,避免层层 Promise:
typescript
import { of } from 'rxjs'
import { mergeMap } from 'rxjs/operators'
const source$ = of('Hello')
const example$ = source$.pipe(mergeMap((val) => of(`${val} World!`)))
example$.subscribe((val) => console.log(val)) // Hello World!
复杂业务(改参后重试)用 switchMap + catchError 更贴近 RxJS 风格;不熟悉 RxJS 时,firstValueFrom + async/await 更易维护。
踩坑与注意
| 现象 | 原因 | 处理 |
|---|---|---|
console.log 打不出响应体 |
拿到的是 Observable | subscribe 或 firstValueFrom |
| 请求未发出就结束 | 未订阅 Observable | 必须 subscribe / await firstValueFrom |
| Nest 9 直接用 axios 报错 | 未走 HttpModule | 注入 HttpService |
| Token 刷新竞态 | 并发多次刷新 | 用锁或 switchMap 串行化 |
- 新项目优先:
import { firstValueFrom } from 'rxjs',代码更短、错误栈更清晰。 - 全局超时、重试可在
HttpModule.register({ timeout: 5000 })配置。
小结
Nest 9+ 第三方 HTTP 调用走 HttpService → Observable → firstValueFrom / subscribe。嵌套请求可 Promise 封装(本文历史写法),也可用 RxJS 算子链;按团队熟悉度二选一即可。


全部评论(0)