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

@soybeanjs/request

v0.0.3

Published

A request library based on Axios

Downloads

178

Readme

@soybeanjs/request

English | 简体中文

一个基于 Axios 封装的轻量级、类型安全的 HTTP 请求库,提供优雅的 API 设计和强大的功能支持。

✨ 特性

  • 🎯 类型安全:完整的 TypeScript 类型支持,智能类型推导
  • 🔄 双实例模式:支持标准请求实例和扁平化响应实例
  • 📦 文件下载:自动解析文件名和内容类型,支持多种文件格式
  • 🎣 生命周期钩子:提供完整的请求生命周期管理
  • 🔁 自动重试:集成 axios-retry,支持请求失败自动重试
  • 🛡️ 错误处理:统一的错误处理机制,支持业务错误和网络错误
  • 📝 响应转换:灵活的响应数据转换功能
  • 🎨 状态管理:内置状态管理,可在实例间共享数据

📦 安装

# npm
npm install @soybeanjs/request

# yarn
yarn add @soybeanjs/request

# pnpm
pnpm add @soybeanjs/request

🚀 快速开始

基础使用

import { createRequest } from '@soybeanjs/request';

interface ApiResponse<T = any> {
  code: number;
  data: T;
  message: string;
}

// 创建请求实例
const request = createRequest(
  {
    baseURL: 'https://api.example.com',
    timeout: 10000
  },
  {
    // 转换响应数据
    // !!!注意这里一定要给response指定类型,这样才能有类型推导
    transform: (response: AxiosResponse<ApiResponse>) => {
      return response.data.result;
    },
    // 请求前拦截
    onRequest: async config => {
      // 添加 token
      config.headers.Authorization = `Bearer ${getToken()}`;
      return config;
    },
    // 判断后端业务是否成功
    isBackendSuccess: response => {
      return response.data.code === 200;
    },
    // 后端业务失败处理
    onBackendFail: async (response, instance) => {
      // 处理 token 过期等情况
      if (response.data.code === 401) {
        await refreshToken();
        // 重新发起请求
        return instance.request(response.config);
      }
    },
    // 错误处理
    onError: async error => {
      console.error('Request failed:', error.message);
    }
  }
);

// 发起请求
const data = await request({
  url: '/users',
  method: 'GET'
});

扁平化响应实例

不抛出异常,通过返回值判断成功或失败:

import { createFlatRequest } from '@soybeanjs/request';

const flatRequest = createFlatRequest(axiosConfig, options);

const { data, error, response } = await flatRequest({
  url: '/users',
  method: 'GET'
});

if (error) {
  console.error('Request failed:', error);
} else {
  console.log('Success:', data);
}

📖 核心概念

RequestOption 配置项

| 配置项 | 类型 | 必填 | 说明 | | ------------------ | ---------- | ---- | ------------------------------------ | | transform | Function | 是 | 转换响应数据为业务数据 | | onRequest | Function | 否 | 请求前拦截器,可添加 token 等 | | isBackendSuccess | Function | 是 | 判断后端业务逻辑是否成功 | | onBackendFail | Function | 否 | 后端业务失败回调,如处理 token 过期 | | onError | Function | 否 | 请求错误处理,如显示错误提示 | | defaultState | Object | 否 | 默认状态对象 | | backendErrorFlag | string | 否 | 后端错误标识,默认 'BACKEND_ERROR' | | backendErrorMsg | string | 否 | 后端错误消息 |

请求处理流程

用户发起请求
    ↓
onRequest 拦截器(添加 token 等)
    ↓
发送 HTTP 请求
    ↓
接收响应
    ↓
transformResponse(json类型时自动转换 blob/arraybuffer)
    ↓
判断 responseType
    ├─ json → isBackendSuccess?
    │   ├─ 成功 → transform → 返回业务数据
    │   └─ 失败 → onBackendFail → onError
    ├─ 文件类型 → 返回文件信息对象
    └─ 其他 → 返回原始数据

🎯 高级功能

1. 文件下载

支持自动解析文件名和内容类型:

// 下载文件
const fileData = await request({
  url: '/download/report.pdf',
  method: 'GET',
  responseType: 'blob'
});

// fileData 包含:
// {
//   file: Blob,
//   filename: 'report.pdf',
//   contentType: 'application/pdf'
// }

// 自定义文件名解析
const fileData = await request({
  url: '/download/file',
  responseType: 'blob',
  getFileName: response => {
    // 自定义解析逻辑
    return 'custom-filename.pdf';
  }
});

// 使用 [fileSaver](https://github.com/eligrey/FileSaver.js) 下载文件
import { saveAs } from 'file-saver';

saveAs(fileData.file, fileData.filename);

// 或者自行通过创建链接下载
const url = URL.createObjectURL(fileData.file);
const a = document.createElement('a');
a.href = url;
a.download = fileData.filename;
a.click();
URL.revokeObjectURL(url);

// 以上通过链接下载的方式等同于使用内置的 downloadFile 工具函数
import { downloadFile } from '@soybeanjs/request';

支持的文件类型:

  • blobFileResponseData<Blob>
  • arraybufferFileResponseData<ArrayBuffer>
  • streamFileResponseData<ReadableStream<Uint8Array>>

2. 响应类型支持

// JSON(默认), 需要添加一个范型参数指定业务数据类型,其他类型无需指定

interface UserData {
  id: number;
  name: string;
}
const data = await request<UserData>({
  url: '/users/123'
});

// 文本
const text = await request({
  url: '/data.csv',
  responseType: 'text'
});

// HTML/XML 文档
const doc = await request({
  url: '/template.html',
  responseType: 'document'
});

// Blob(文件)
const file = await request({
  url: '/download/image.png',
  responseType: 'blob'
});

// ArrayBuffer
const buffer = await request({
  url: '/download/data.bin',
  responseType: 'arraybuffer'
});

3. 状态管理

在请求实例中共享状态:

interface CustomState {
  token: string;
  userId: number;
}

const request = createRequest(axiosConfig, {
  defaultState: {
    token: '',
    userId: 0
  } as CustomState
  // ...其他配置
});

// 访问和修改状态
request.state.token = 'new-token';
request.state.userId = 123;

// 在钩子中使用状态
onRequest: config => {
  config.headers.Authorization = `Bearer ${request.state.token}`;
  return config;
};

4. 自动重试

const request = createRequest(
  {
    baseURL: 'https://api.example.com',
    // axios-retry 配置
    retries: 3,
    retryDelay: retryCount => retryCount * 1000,
    retryCondition: error => {
      // 仅在网络错误或 5xx 错误时重试
      return !error.response || error.response.status >= 500;
    }
  },
  options
);

5. 类型推导

完整的 TypeScript 类型支持:

interface User {
  id: number;
  name: string;
}

interface ApiResponse<T = any> {
  code: number;
  data: T;
  message: string;
}

// ResponseData:后端原始响应类型
// ApiData:业务数据类型
const request = createRequest(axiosConfig, {
  transform: (response: AxiosResponse<ApiResponse>) => response.data.data
});

// 类型推导:data 的类型是 ApiResponse<User>
const user = await request<User>({
  url: '/users/123'
});

🛠️ 实用工具

parseContentDisposition

解析 Content-Disposition 响应头获取文件名:

import { parseContentDisposition } from '@soybeanjs/request';

const filename = parseContentDisposition("attachment; filename*=UTF-8''%E6%96%87%E4%BB%B6.pdf");
// '文件.pdf'

支持的格式:

  • RFC 5987 编码:filename*=UTF-8''example%20file.pdf
  • 普通格式:filename="example.pdf"filename=example.pdf

📝 完整示例

带认证的 API 请求

import { createRequest } from '@soybeanjs/request';

interface ApiResponse<T = any> {
  code: number;
  data: T;
  message: string;
}

interface User {
  id: number;
  name: string;
  email: string;
}

// 创建请求实例
const request = createRequest(
  {
    baseURL: 'https://api.example.com',
    timeout: 10000
  },
  {
    // 转换响应数据
    transform: (response: AxiosResponse<ApiResponse>) => {
      return response.data.data;
    },
    // 请求拦截
    onRequest: async config => {
      const token = localStorage.getItem('token');
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    },
    // 判断业务成功
    isBackendSuccess: response => {
      return response.data.code === 200;
    },
    // 业务失败处理
    onBackendFail: async (response, instance) => {
      const { code } = response.data;

      // Token 过期,刷新后重试
      if (code === 401) {
        const newToken = await refreshToken();
        localStorage.setItem('token', newToken);

        // 更新请求头并重试
        response.config.headers.Authorization = `Bearer ${newToken}`;
        return instance.request(response.config);
      }
    },
    // 错误处理
    onError: async error => {
      showMessage(error.response?.data.message || error.message);
    }
  }
);

// 使用示例

// 1. 获取用户信息
async function getUser(id: number) {
  const user = await request<User>({
    url: `/users/${id}`,
    method: 'GET'
  });
  return user;
}

// 2. 创建用户
async function createUser(data: Partial<User>) {
  const user = await request<User>({
    url: '/users',
    method: 'POST',
    data
  });
  return user;
}

// 3. 下载文件
async function downloadReport(reportId: string) {
  const fileData = await request({
    url: `/reports/${reportId}/download`,
    method: 'GET',
    responseType: 'blob'
  });

  // 触发浏览器下载
  const url = URL.createObjectURL(fileData.file);
  const a = document.createElement('a');
  a.href = url;
  a.download = fileData.filename || 'report.pdf';
  a.click();
  URL.revokeObjectURL(url);
}

// 4. 上传文件
async function uploadFile(file: File) {
  const formData = new FormData();
  formData.append('file', file);

  const result = await request({
    url: '/upload',
    method: 'POST',
    data: formData,
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  });
  return result;
}

扁平化响应实例

import { createFlatRequest } from '@soybeanjs/request';

const flatRequest = createFlatRequest(axiosConfig, options);

// 所有请求都返回 { data, error, response }
async function safeGetUser(id: number) {
  const { data, error } = await flatRequest<User>({
    url: `/users/${id}`
  });

  if (error) {
    console.error('获取用户失败:', error.message);
    return null;
  }

  return data;
}

🔧 API 参考

createRequest

创建标准请求实例。

function createRequest<ResponseData, ApiData, State>(
  axiosConfig?: CreateAxiosDefaults,
  options?: Partial<RequestOption<ResponseData, ApiData, State>>
): RequestInstance<ApiData, State>;

createFlatRequest

创建扁平化请求实例,不抛出异常。

function createFlatRequest<ResponseData, ApiData, State>(
  axiosConfig?: CreateAxiosDefaults,
  options?: Partial<RequestOption<ResponseData, ApiData, State>>
): FlatRequestInstance<ResponseData, ApiData, State>;

类型定义

// 请求实例
interface RequestInstance<ApiData, State> {
  <T = ApiData, R extends ResponseType = 'json'>(config: CustomAxiosRequestConfig<R>): Promise<MappedType<R, T>>;
  state: State;
}

// 扁平化请求实例
interface FlatRequestInstance<ResponseData, ApiData, State> {
  <T = ApiData, R extends ResponseType = 'json'>(
    config: CustomAxiosRequestConfig<R>
  ): Promise<FlatResponseData<ResponseData, MappedType<R, T>>>;
  state: State;
}

// 扁平化响应数据
type FlatResponseData<ResponseData, ApiData> =
  | { data: ApiData; error: null; response: AxiosResponse<ResponseData> }
  | { data: null; error: AxiosError<ResponseData>; response: AxiosResponse<ResponseData> };

// 文件响应数据
interface FileResponseData<T = Blob | ArrayBuffer | ReadableStream> {
  file: T;
  filename: string;
  contentType: string;
}

// 响应类型映射
type ResponseType = 'json' | 'blob' | 'arraybuffer' | 'stream' | 'text' | 'document';

❓ FAQ

为什么需要两种请求实例?

  • createRequest:适合大多数场景,请求失败会抛出异常,可使用 try-catch 捕获
  • createFlatRequest:适合需要统一处理成功和失败的场景,不会抛出异常,通过返回值判断

如何处理业务错误和网络错误?

{
  // 所有的请求错误,包括后端错误,网络错误和 HTTP 错误通过 onError 处理
  onError: async (error) => {
    if (!error.response) {
      // 网络错误
    } else if (error.response.status >= 500) {
      // 服务器错误
    }
  },

  // 后端业务错误通过 isBackendSuccess 和 onBackendFail 处理
  isBackendSuccess: (response) => response.data.code === 200,
  onBackendFail: async (response) => {
    // 处理业务错误,如 code: 401, 403 等
  }
}

文件下载如何获取文件名?

库会自动从响应头的 Content-Disposition 解析文件名。如果需要自定义:

const fileData = await request({
  url: '/download',
  responseType: 'blob',
  getFileName: response => {
    // 自定义解析逻辑
    return 'my-file.pdf';
  }
});

如何实现请求取消?

const controller = new AbortController();

const promise = request({
  url: '/users',
  signal: controller.signal
});

// 取消请求
controller.abort();

📄 License

MIT License © 2026 Soybean