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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@cimom/vben-effects-request

v5.6.9

Published

HTTP 请求客户端工具包,基于 Axios 封装,提供了请求拦截、响应处理、错误处理、取消请求等功能,简化了 API 调用的实现。

Readme

@cimom/vben-effects-request

HTTP 请求客户端工具包,基于 Axios 封装,提供了请求拦截、响应处理、错误处理、取消请求等功能,简化了 API 调用的实现。

安装

npm install @cimom/vben-effects-request

基本使用

创建请求客户端

import { createRequestClient } from '@cimom/vben-effects-request';

// 创建请求客户端实例
const requestClient = createRequestClient({
  // 基础URL
  baseURL: 'https://api.example.com',
  // 超时时间(毫秒)
  timeout: 10000,
  // 请求头
  headers: {
    'Content-Type': 'application/json',
  },
  // 是否携带凭证(cookies)
  withCredentials: true,
});

// 导出实例供应用使用
export default requestClient;

发送请求

import requestClient from './request';

// GET 请求
async function fetchUsers() {
  const { data } = await requestClient.get('/users', {
    params: {
      page: 1,
      limit: 10,
    },
  });
  return data;
}

// POST 请求
async function createUser(userData) {
  const { data } = await requestClient.post('/users', userData);
  return data;
}

// PUT 请求
async function updateUser(id, userData) {
  const { data } = await requestClient.put(`/users/${id}`, userData);
  return data;
}

// DELETE 请求
async function deleteUser(id) {
  const { data } = await requestClient.delete(`/users/${id}`);
  return data;
}

使用拦截器

import requestClient from './request';

// 请求拦截器
requestClient.interceptors.request.use(
  (config) => {
    // 在发送请求前做些什么
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);
  },
);

// 响应拦截器
requestClient.interceptors.response.use(
  (response) => {
    // 对响应数据做些什么
    const { code, data, message } = response.data;

    if (code === 0) {
      return data;
    } else {
      // 处理业务错误
      console.error(message);
      return Promise.reject(new Error(message));
    }
  },
  (error) => {
    // 对响应错误做些什么
    if (error.response) {
      // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
      switch (error.response.status) {
        case 401:
          // 未授权,跳转到登录页
          break;
        case 403:
          // 禁止访问
          break;
        case 404:
          // 资源不存在
          break;
        case 500:
          // 服务器错误
          break;
        default:
          // 其他错误
          break;
      }
    } else if (error.request) {
      // 请求已经成功发起,但没有收到响应
      console.error('网络错误,请检查您的网络连接');
    } else {
      // 发送请求时出了点问题
      console.error('请求配置错误', error.message);
    }

    return Promise.reject(error);
  },
);

API 参考

createRequestClient

创建请求客户端实例。

function createRequestClient(config?: RequestClientConfig): RequestClient;

RequestClientConfig 配置项

| 配置项 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | baseURL | string | '' | 基础URL,将自动加在URL前面 | | timeout | number | 10000 | 请求超时时间(毫秒) | | headers | Record<string, string> | {} | 请求头 | | withCredentials | boolean | false | 是否携带凭证(cookies) | | responseType | 'arraybuffer' \| 'blob' \| 'document' \| 'json' \| 'text' \| 'stream' | 'json' | 响应数据类型 | | maxRedirects | number | 5 | 最大重定向次数 | | validateStatus | (status: number) => boolean | status >= 200 && status < 300 | 定义哪些HTTP状态码是有效的 | | paramsSerializer | (params: any) => string | - | 参数序列化函数 | | cancelToken | CancelToken | - | 取消请求的令牌 | | signal | AbortSignal | - | 取消请求的信号 | | onUploadProgress | (progressEvent: any) => void | - | 上传进度回调 | | onDownloadProgress | (progressEvent: any) => void | - | 下载进度回调 |

RequestClient 实例方法

| 方法名 | 参数 | 返回值 | 说明 | | --- | --- | --- | --- | | get | (url: string, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 GET 请求 | | post | (url: string, data?: any, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 POST 请求 | | put | (url: string, data?: any, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 PUT 请求 | | delete | (url: string, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 DELETE 请求 | | head | (url: string, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 HEAD 请求 | | options | (url: string, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 OPTIONS 请求 | | patch | (url: string, data?: any, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 PATCH 请求 | | request | (config: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送自定义请求 |

拦截器

// 请求拦截器
requestClient.interceptors.request.use(
  (config) => config,
  (error) => Promise.reject(error),
);

// 响应拦截器
requestClient.interceptors.response.use(
  (response) => response,
  (error) => Promise.reject(error),
);

取消请求

// 使用 CancelToken
const source = requestClient.CancelToken.source();

requestClient.get('/users', {
  cancelToken: source.token,
});

// 取消请求
source.cancel('请求被用户取消');

// 使用 AbortController (更现代的方式)
const controller = new AbortController();

requestClient.get('/users', {
  signal: controller.signal,
});

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

预设拦截器

该包提供了一些常用的预设拦截器,可以直接使用:

import {
  createRequestClient,
  presetInterceptors,
} from '@cimom/vben-effects-request';

const requestClient = createRequestClient({
  baseURL: 'https://api.example.com',
});

// 使用预设拦截器
requestClient.interceptors.request.use(
  presetInterceptors.addTokenInterceptor('Bearer', () =>
    localStorage.getItem('token'),
  ),
);

requestClient.interceptors.response.use(
  presetInterceptors.transformResponseInterceptor(),
  presetInterceptors.handleErrorInterceptor(),
);

可用的预设拦截器

| 拦截器名称 | 说明 | | ------------------------------ | ---------------------------------- | | addTokenInterceptor | 添加认证令牌到请求头 | | transformResponseInterceptor | 转换响应数据,提取有效数据 | | handleErrorInterceptor | 处理错误响应,包括网络错误、超时等 | | addTimestampInterceptor | 为 GET 请求添加时间戳,避免缓存 | | loggerInterceptor | 记录请求和响应日志 |

高级用法

创建多个请求实例

import { createRequestClient } from '@cimom/vben-effects-request';

// 创建用于普通 API 的请求客户端
export const apiClient = createRequestClient({
  baseURL: 'https://api.example.com',
});

// 创建用于文件上传的请求客户端
export const uploadClient = createRequestClient({
  baseURL: 'https://upload.example.com',
  timeout: 60000, // 更长的超时时间
  headers: {
    'Content-Type': 'multipart/form-data',
  },
});

// 创建用于第三方 API 的请求客户端
export const thirdPartyClient = createRequestClient({
  baseURL: 'https://third-party-api.com',
  withCredentials: false,
});

请求重试

import { createRequestClient } from '@cimom/vben-effects-request';
import axios from 'axios';

const requestClient = createRequestClient({
  baseURL: 'https://api.example.com',
});

// 添加响应拦截器实现请求重试
requestClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    const config = error.config;

    // 设置重试次数和计数器
    if (!config || !config.retry) {
      config.retry = 3;
      config.retryCount = 0;
      config.retryDelay = 1000;
    }

    // 检查是否还有重试次数
    if (config.retryCount < config.retry) {
      config.retryCount += 1;

      // 延迟重试
      await new Promise((resolve) => {
        setTimeout(resolve, config.retryDelay * config.retryCount);
      });

      // 重新发送请求
      return requestClient(config);
    }

    return Promise.reject(error);
  },
);

请求队列和并发控制

import { createRequestClient } from '@cimom/vben-effects-request';

const requestClient = createRequestClient({
  baseURL: 'https://api.example.com',
});

// 创建请求队列
class RequestQueue {
  private queue = [];
  private maxConcurrent = 3;
  private runningCount = 0;

  constructor(maxConcurrent = 3) {
    this.maxConcurrent = maxConcurrent;
  }

  async add(requestFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({
        requestFn,
        resolve,
        reject,
      });

      this.processQueue();
    });
  }

  private async processQueue() {
    if (this.runningCount >= this.maxConcurrent || this.queue.length === 0) {
      return;
    }

    this.runningCount += 1;
    const { requestFn, resolve, reject } = this.queue.shift();

    try {
      const result = await requestFn();
      resolve(result);
    } catch (error) {
      reject(error);
    } finally {
      this.runningCount -= 1;
      this.processQueue();
    }
  }
}

// 使用请求队列
const queue = new RequestQueue(3);

// 添加请求到队列
async function fetchData(id) {
  return queue.add(() => requestClient.get(`/data/${id}`));
}

// 批量请求
async function fetchBatch(ids) {
  return Promise.all(ids.map((id) => fetchData(id)));
}

请求缓存

import { createRequestClient } from '@cimom/vben-effects-request';

const requestClient = createRequestClient({
  baseURL: 'https://api.example.com',
});

// 简单的内存缓存
const cache = new Map();

// 添加请求拦截器检查缓存
requestClient.interceptors.request.use((config) => {
  // 只缓存 GET 请求
  if (config.method.toLowerCase() === 'get') {
    const cacheKey = `${config.url}?${new URLSearchParams(config.params).toString()}`;

    // 检查缓存是否存在且未过期
    const cachedData = cache.get(cacheKey);
    if (cachedData && Date.now() < cachedData.expiry) {
      // 返回缓存数据
      return Promise.reject({
        __CACHE__: true,
        data: cachedData.data,
      });
    }
  }

  return config;
});

// 添加响应拦截器存储缓存
requestClient.interceptors.response.use((response) => {
  // 只缓存 GET 请求
  if (response.config.method.toLowerCase() === 'get') {
    const cacheKey = `${response.config.url}?${new URLSearchParams(response.config.params).toString()}`;

    // 缓存响应数据,设置过期时间为 5 分钟
    cache.set(cacheKey, {
      data: response.data,
      expiry: Date.now() + 5 * 60 * 1000,
    });
  }

  return response;
});

// 处理缓存错误
requestClient.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.__CACHE__) {
      return Promise.resolve({
        data: error.data,
        __fromCache__: true,
      });
    }

    return Promise.reject(error);
  },
);

示例

用户认证 API

import requestClient from './request';

// 用户认证相关 API
export const authApi = {
  // 登录
  login: (username: string, password: string) => {
    return requestClient.post('/auth/login', { username, password });
  },

  // 注册
  register: (userData: any) => {
    return requestClient.post('/auth/register', userData);
  },

  // 获取当前用户信息
  getCurrentUser: () => {
    return requestClient.get('/auth/me');
  },

  // 刷新令牌
  refreshToken: (refreshToken: string) => {
    return requestClient.post('/auth/refresh', { refreshToken });
  },

  // 登出
  logout: () => {
    return requestClient.post('/auth/logout');
  },
};

文件上传

import requestClient from './request';

// 文件上传 API
export const fileApi = {
  // 上传单个文件
  uploadFile: (file: File, onProgress?: (percent: number) => void) => {
    const formData = new FormData();
    formData.append('file', file);

    return requestClient.post('/upload', formData, {
      headers: {
        'Content-Type': 'multipart/form-data',
      },
      onUploadProgress: (progressEvent) => {
        if (onProgress) {
          const percent = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total,
          );
          onProgress(percent);
        }
      },
    });
  },

  // 上传多个文件
  uploadFiles: (files: File[], onProgress?: (percent: number) => void) => {
    const formData = new FormData();
    files.forEach((file, index) => {
      formData.append(`files[${index}]`, file);
    });

    return requestClient.post('/upload/multiple', formData, {
      headers: {
        'Content-Type': 'multipart/form-data',
      },
      onUploadProgress: (progressEvent) => {
        if (onProgress) {
          const percent = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total,
          );
          onProgress(percent);
        }
      },
    });
  },

  // 下载文件
  downloadFile: (fileId: string) => {
    return requestClient.get(`/files/${fileId}/download`, {
      responseType: 'blob',
    });
  },
};

带有分页的数据列表

import requestClient from './request';

// 分页参数接口
interface PaginationParams {
  page: number;
  limit: number;
  sort?: string;
  order?: 'asc' | 'desc';
  search?: string;
}

// 分页响应接口
interface PaginationResponse<T> {
  items: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

// 用户 API
export const userApi = {
  // 获取用户列表
  getUsers: (params: PaginationParams) => {
    return requestClient.get<PaginationResponse<User>>('/users', { params });
  },

  // 获取用户详情
  getUser: (id: string) => {
    return requestClient.get<User>(`/users/${id}`);
  },

  // 创建用户
  createUser: (userData: Partial<User>) => {
    return requestClient.post<User>('/users', userData);
  },

  // 更新用户
  updateUser: (id: string, userData: Partial<User>) => {
    return requestClient.put<User>(`/users/${id}`, userData);
  },

  // 删除用户
  deleteUser: (id: string) => {
    return requestClient.delete(`/users/${id}`);
  },
};

// 用户接口
interface User {
  id: string;
  username: string;
  email: string;
  fullName: string;
  role: string;
  status: 'active' | 'inactive';
  createdAt: string;
  updatedAt: string;
}

请求错误处理

import requestClient from './request';
import { message } from 'ant-design-vue';

// 添加响应拦截器处理错误
requestClient.interceptors.response.use(
  (response) => {
    // 处理成功响应
    return response;
  },
  (error) => {
    // 处理错误响应
    let errorMessage = '未知错误';

    if (error.response) {
      // 服务器返回了错误状态码
      const { status, data } = error.response;

      switch (status) {
        case 400:
          errorMessage = data.message || '请求参数错误';
          break;
        case 401:
          errorMessage = '未授权,请重新登录';
          // 跳转到登录页
          window.location.href = '/login';
          break;
        case 403:
          errorMessage = '拒绝访问';
          break;
        case 404:
          errorMessage = '请求的资源不存在';
          break;
        case 500:
          errorMessage = '服务器错误';
          break;
        default:
          errorMessage = `请求错误 (${status})`;
          break;
      }
    } else if (error.request) {
      // 请求已发送但没有收到响应
      errorMessage = '网络错误,请检查您的网络连接';
    } else {
      // 请求配置出错
      errorMessage = error.message;
    }

    // 显示错误消息
    message.error(errorMessage);

    // 将错误传递给调用者
    return Promise.reject(error);
  },
);

// 使用 try/catch 处理错误
async function fetchData() {
  try {
    const response = await requestClient.get('/data');
    return response.data;
  } catch (error) {
    // 错误已经被拦截器处理,这里可以做额外处理
    console.error('获取数据失败:', error);
    return null;
  }
}