npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gopowerteam/request

v0.3.2

Published

Readme

@gopowerteam/request

npm version npm downloads types

基于适配器模式的 HTTP 请求库。通过可插拔的适配器对接底层 HTTP 客户端(内置 axios),配合插件中间件与响应拦截器,提供类型安全、可扩展、跨运行时(Node / 浏览器)的统一请求方案。

特性

  • 适配器模式 —— RequestAdapter 接口解耦请求调度与底层 HTTP 客户端,内置 AxiosAdapter 可直接使用,也可自行实现对接 fetch / 小程序等运行时
  • 插件中间件 —— 通过 before / after / catch 三段生命周期织入鉴权、loading、日志、埋点等横切逻辑
  • 响应拦截器 —— 用 status / success / error / exception 四类拦截器集中管理"成功判定 / 数据转换 / 错误处理 / 异常兜底"
  • 单例服务 —— RequestService.getInstance() 提供全局唯一请求入口,配置一次,到处可用
  • 路径参数与查询参数 —— 内置 {id} 模板替换与 qs 序列化(数组、嵌套、空值过滤)
  • 智能 Content-Type —— AxiosAdapter 按运行时 body 自动推断(FormData / Blob / URLSearchParams 等),显式设置绝对优先
  • 双模块格式 —— 同时产出 ESM + CJS + .d.ts,Node 与打包工具无缝消费
  • 全量 TypeScript —— 所有接口与选项均带类型,strict 模式友好的

安装

# pnpm
pnpm add @gopowerteam/request

# npm
npm install @gopowerteam/request

# yarn
yarn add @gopowerteam/request

axios 与 qs 为运行时依赖,会自动安装。

快速上手

import { setup, RequestService, RequestMethod } from '@gopowerteam/request'
import { AxiosAdapter } from '@gopowerteam/request/adapters'

// 1. 安装全局配置
setup({
  gateway: 'https://api.example.com',
  adapter: new AxiosAdapter(),
  timeout: 10000,
  interceptors: {
    status: { exec: res => res.status >= 200 && res.status < 300 },
    success: { exec: res => res.data },
    error: { exec: res => res.data },
    exception: { exec: res => console.error('请求异常', res) },
  },
  plugins: [],
})

// 2. 发起请求
async function bootstrap() {
  const request = RequestService.getInstance()

  const data = await request.send({
    path: '/users',
    method: RequestMethod.Get,
    paramsQuery: { page: 1, size: 20 },
  })

  console.log(data)
}

bootstrap()

全局配置

setup(config: RequestSetupConfig) 向 RequestService 注入全局配置,整个应用只需调用一次。

| 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | gateway | string | 是 | API 网关地址,会作为请求的 baseURL | | adapter | RequestAdapter | 否 | 底层 HTTP 客户端适配器,不传时调用 send 会抛错 | | timeout | number | 否 | 请求超时时间(毫秒),透传给适配器 | | qs | IStringifyOptions | 否 | qs 序列化选项,覆盖默认的数组 / 空值行为 | | interceptors | 见下表 | 是 | 四类响应拦截器,缺一不可 | | plugins | RequestPlugin[] | 否 | 全局插件,对所有请求生效 |

interceptors 字段:

| 拦截器 | 触发时机 | 返回值作用 | | --- | --- | --- | | status | 每次响应后 | 返回布尔值,决定走 success 还是 error | | success | status 返回 true | exec 的返回值作为 send 的 resolved 值 | | error | status 返回 false | exec 的返回值作为 send 的 rejected 值 | | exception | 网络层 / 适配器层抛错 | 用于兜底日志或上报,不参与 send 的返回 |

发起请求

通过 RequestService.getInstance().send(options, plugins?) 发起请求。

RequestSendOptions

| 字段 | 类型 | 说明 | | --- | --- | --- | | path | string | 请求路径,支持 {id} 形式的路径参数 | | method | RequestMethod \| string | HTTP 方法,推荐用 RequestMethod 枚举 | | service | string | 服务前缀,会拼接为 /{service}/{path} | | headers | Record<string, string \| boolean> | 请求头,false 表示删除该头(axios 语义) | | paramsPath | Record<string, string \| number> | 路径参数,替换 path 中的 {key} | | paramsQuery | Record<string, any> | 查询参数,经 qs 序列化 | | paramsBody | any | 请求体 | | signal | AbortSignal | 终止请求的信号,配合 AbortController 使用 |

GET 查询参数

await request.send({
  path: '/users',
  method: RequestMethod.Get,
  paramsQuery: { keyword: 'foo', roles: ['admin', 'user'] },
})
// GET /users?keyword=foo&roles=admin&roles=user

POST 请求体

await request.send({
  path: '/users',
  method: RequestMethod.Post,
  paramsBody: { name: 'Alice', age: 28 },
})

路径参数

await request.send({
  path: '/users/{id}/posts/{postId}',
  method: RequestMethod.Get,
  paramsPath: { id: 42, postId: 'abc' },
})
// GET /users/42/posts/abc

服务前缀

await request.send({
  service: 'user-service',
  path: '/users',
  method: RequestMethod.Get,
})
// GET {gateway}/user-service/users

生成 URL

toURL(options, plugins?) 不发起请求,仅按配置生成完整 URL,常用于下载、外链跳转。

const url = request.toURL({
  path: '/export',
  method: RequestMethod.Get,
  paramsQuery: { type: 'csv', ids: [1, 2, 3] },
})
// https://api.example.com/export?type=csv&ids=1&ids=2&ids=3

插件

实现 RequestPlugin 接口即可织入请求生命周期。插件既可作为全局配置,也可按请求传入 send / toURL 的第二个参数。

生命周期

| 钩子 | 触发时机 | 用途 | | --- | --- | --- | | before(options, appendParams) | 请求发出前 | 改写请求参数、注入 header | | after(response, options) | 响应成功转换后 | 成功分支的副作用 | | catch(response, options) | 响应异常转换后 | 异常分支的副作用 |

before 的第二个参数 appendParams 可向底层适配器追加额外配置(例如透传 axios 的 responseType):

function withResponseType(): RequestPlugin {
  return {
    before: (_options, appendParams) => {
      appendParams({ responseType: 'blob' })
    },
  }
}

await request.send(
  { path: '/files/1', method: RequestMethod.Get },
  [withResponseType()],
)

注入鉴权 Token(全局插件示例)

setup({
  // ...其余配置
  plugins: [
    {
      before: (options) => {
        options.headers = {
          ...options.headers,
          Authorization: `Bearer ${getToken()}`,
        }
      },
    },
  ],
})

Loading 切换(请求级插件示例)

class LoadingPlugin implements RequestPlugin {
  before() { showLoading() }
  after() { hideLoading() }
  catch() { hideLoading() }
}

await request.send(
  { path: '/users', method: RequestMethod.Get },
  [new LoadingPlugin()],
)

全局插件与请求级插件都会执行;before 先执行全局、再执行请求级,after / catch 同理。

请求取消

手动传入 signal

RequestSendOptions 支持 Web 标准的 AbortSignal,配合 AbortController 终止请求:

const controller = new AbortController()

request.send({
  path: '/user-service/api/users',
  method: RequestMethod.Get,
  signal: controller.signal,
})

controller.abort()

被取消的请求会走异常流程,AdapterResponse 上的 canceled 标记为 true, 可在 exception / error 拦截器中静默处理:

class ExceptionInterceptor implements ResponseInterceptor {
  exec(res: AdapterResponse) {
    if (res.canceled) {
      return res // 手动取消,静默处理
    }
    // 真实异常正常提示...
    return res
  }
}

调用方也可以在 catch 中自行区分:

request.send({ path: '/users', method: RequestMethod.Get, signal: controller.signal })
  .catch((res: AdapterResponse) => {
    if (res.canceled) return
    // 处理真实错误
  })

AbortService 内置插件

从子路径 @gopowerteam/request/plugins 导入。每个实例是一个取消作用域, abort() 终止该实例发起的所有进行中请求;请求结束后自动回收,可放心复用:

import { AbortService } from '@gopowerteam/request/plugins'

const requestAbort = new AbortService()

request.send(
  { path: '/users', method: RequestMethod.Get },
  [requestAbort],
)

// 需要时终止该作用域内全部进行中请求
requestAbort.abort()

配合 request-generate 生成的服务使用(利用每个生成方法都暴露的 requestPlugins 参数):

const [meituan, order] = useRequest(
  ({ MeituanService }) => MeituanService,
  ({ OrderService }) => OrderService,
)

const requestAbort = new AbortService()

meituan.AccountService.getBalance(accountId, [requestAbort])
order.OrderService.listByAccount({ accountId }, [requestAbort])

// 组件卸载时终止本组件全部请求
onBeforeUnmount(() => requestAbort.abort())

搜索防抖场景:每次发新请求前 abort() 旧请求,再用同一实例发起新请求即可:

const searchAbort = new AbortService()

async function search(keyword: string) {
  searchAbort.abort() // 终止上一次进行中的请求

  try {
    results.value = await SearchService.query({ keyword }, [searchAbort])
  }
  catch (res: any) {
    if (!res.canceled) console.error(res.data) // 取消静默,真实错误才提示
  }
}

注意:toURL() 也会触发插件 before 但没有响应生命周期,URL 生成场景无需传入 AbortService。

自定义适配器支持 signal

核心层只透传 signal,取消的识别与终止由各适配器自行实现。小程序等运行时可按下述模式桥接(微信 RequestTask.abort() 为例):

class WxRequestAdapter implements RequestAdapter {
  request(options: RequestAdapterOptions) {
    return new Promise((resolve, reject) => {
      const task = wx.request({
        url: options.baseURL + options.pathURL,
        method: options.method,
        data: options.paramsBody,
        success: resolve,
        fail: reject,
      })

      if (!options.signal) {
        return
      }

      const onAbort = () => task.abort()

      // 注意处理已终止的 signal(立即触发)与 once 解绑
      if (options.signal.aborted) {
        onAbort()
      }
      else {
        options.signal.addEventListener('abort', onAbort, { once: true })
      }
    })
  }

  transformResponse(response: any) {
    return response
  }

  transformException(error: any): AdapterResponse {
    return {
      data: {},
      status: 400,
      statusText: '',
      headers: {},
      canceled: (error.errMsg || '').includes('abort'),
    }
  }
}

运行时前提:AbortService 依赖 AbortController(浏览器 / Node 18+ / 较新微信基础库均内置)。 不使用 signal 的自定义适配器不受任何影响。

响应拦截器

实现 ResponseInterceptor 接口即可。下面是一套常见用法:

import type { AdapterResponse } from '@gopowerteam/request'

// 判定业务是否成功(决定走 success 还是 error)
class StatusInterceptor implements ResponseInterceptor {
  exec(res: AdapterResponse) {
    return res.status >= 200 && res.status < 300
      && res.data?.code === 0
  }
}

// 成功:剥离外层信封,只返回业务数据
class SuccessInterceptor implements ResponseInterceptor {
  exec(res: AdapterResponse) {
    return res.data?.data
  }
}

// 业务失败:统一错误结构,供调用方 .catch 处理
class ErrorInterceptor implements ResponseInterceptor {
  exec(res: AdapterResponse) {
    return {
      code: res.data?.code,
      message: res.data?.message ?? '请求失败',
      raw: res.data,
    }
  }
}

// 网络层 / 适配器层异常:兜底上报
class ExceptionInterceptor implements ResponseInterceptor {
  exec(res: AdapterResponse) {
    console.error('[request exception]', res)
  }
}

适配器

适配器负责把 RequestService 的抽象请求转换为底层 HTTP 客户端调用。库内提供开箱即用的 AxiosAdapter,也可自行实现 RequestAdapter 接口对接任意运行时。

使用 AxiosAdapter

import { AxiosAdapter } from '@gopowerteam/request/adapters'

setup({
  // ...其余配置
  adapter: new AxiosAdapter(),
})

AxiosAdapter 行为要点:

  • 复用一个 axios 实例,默认 Content-Type: application/json
  • FormData / URLSearchParams → 自动让出 Content-Type,由浏览器 / axios 补 boundary
  • Blob → 兜底 application/octet-stream
  • 调用方显式设置 Content-Type 时绝对优先(大小写不敏感)
  • 查询参数默认 arrayFormat: 'repeat'、skipNulls、allowDots,可通过 setup 的 qs 选项覆盖

自定义适配器

import type {
  AdapterResponse,
  RequestAdapter,
  RequestAdapterOptions,
  RequestSetupConfig,
} from '@gopowerteam/request'

class FetchAdapter implements RequestAdapter {
  private config!: RequestSetupConfig

  injectConfig(config: RequestSetupConfig) {
    this.config = config
  }

  async request(options: RequestAdapterOptions): Promise<AdapterResponse> {
    const res = await fetch(`${options.baseURL}${options.pathURL}`, {
      method: options.method,
      headers: options.headers as Record<string, string>,
      body: options.paramsBody ? JSON.stringify(options.paramsBody) : undefined,
    })

    return {
      status: res.status,
      statusText: res.statusText,
      headers: Object.fromEntries(res.headers.entries()),
      data: await res.json(),
    }
  }

  transformResponse(response: AdapterResponse): AdapterResponse {
    return response
  }

  transformException(exception: any): AdapterResponse {
    return {
      data: {},
      status: exception?.status ?? 400,
      statusText: exception?.statusText ?? '',
      headers: {},
    }
  }
}

API 导出

主入口 @gopowerteam/request

| 导出 | 类型 | 说明 | | --- | --- | --- | | setup | (config: RequestSetupConfig) => void | 安装全局配置 | | RequestService | 类 | 单例请求服务,getInstance() 获取实例 | | RequestMethod | 枚举 | Get / Post / Put / Delete / Patch / Options / Head | | RequestGenerateType | 枚举 | Request / URL,配合生成器使用 | | RequestSetupConfig | 接口 | 全局配置 | | RequestSendOptions | 接口 | 单次请求选项 | | RequestPlugin | 接口 | 插件契约 | | PluginLifecycle | 枚举 | 插件生命周期标识 | | ResponseInterceptor | 接口 | 响应拦截器契约 | | RequestAdapter | 接口 | 适配器契约 | | RequestAdapterOptions | 接口 | 传给适配器的请求选项 | | AdapterResponse | 接口 | 适配器返回的统一响应结构 |

适配器入口 @gopowerteam/request/adapters

| 导出 | 类型 | 说明 | | --- | --- | --- | | AxiosAdapter | 类 | 基于 axios 的内置适配器 |

插件入口 @gopowerteam/request/plugins

| 导出 | 类型 | 说明 | | --- | --- | --- | | AbortService | 类 | 作用域级请求终止插件,abort() 终止该实例全部进行中请求 |

相关生态

  • @gopowerteam/request-generate —— OpenAPI / Swagger 规范自动生成类型安全的 Service 代码(CLI + Vite 插件),生成的代码直接消费本库的 RequestService。

License

MIT (c) gopowerteam

仓库地址:github.com/gopowerteam/package.request(本包位于 packages/request)