Skip to content

Wrapper 模式

适用场景:给非 Axios 的请求方式(fetch、wx.request、自定义 SDK 等)接入全局守护,或者只想给单个请求函数加保护。

为什么需要 Wrapper?

Wrapper 就是为给其他非 Axios 的请求方式,提供同等的全局治理能力 —— 同样支持 rules、defaults、notify、logger。

用法一:非 Axios 请求全局接入

项目使用 fetch / wx.request / 自定义 SDK 等,希望像 Axios 模式一样拥有全局配置、规则匹配和消息通知。

第一步:将请求函数封装成 Promise 风格(如果原本就是 Promise 可跳过)

javascript
function wxRequest(config) {
  return new Promise((resolve, reject) => {
    wx.request({
      ...config,
      success: (res) => resolve(res),
      fail: (err) => reject(err)
    });
  });
}

第二步:通过 setupRequestGuard 接入全局守护

javascript
import { setupRequestGuard, ConsoleRequestGuardLogger } from '@hydd/request-guard/core';
import { duplicate, retry } from '@hydd/request-guard/capabilities';

const guardedRequest = setupRequestGuard(wxRequest, {
  capabilities: [
    duplicate(),
    retry()
  ],
  notify: (payload) => wx.showToast({ title: payload.message, icon: 'none' }),
  logger: new ConsoleRequestGuardLogger({ devOnly: true }),
  // 全局规则 — 和 Axios 模式写法完全一致
  rules: [
    { method: 'post', duplicate: { strategy: 'block', message: '正在提交中' } },
    { method: 'get', retry: { attempts: 2, delay: 500 } }
  ]
});

// 使用方式和原来一样,传入标准 config 对象即可
const res = await guardedRequest({
  url: '/api/order/submit',
  method: 'POST',
  data: { orderId: '12345' },
  header: { 'Content-Type': 'application/json' }
});

// 某个请求需要单独配置(请求级优先级最高)
await guardedRequest({
  url: '/api/important',
  method: 'GET',
  requestGuard: { retry: { attempts: 5 } }
});

// 某个请求跳过守护
await guardedRequest({
  url: '/api/health',
  method: 'GET',
  requestGuard: false  // 总开关关闭
});

// 卸载守护,后续调用直接走原始函数
guardedRequest.uninstall();

TIP

当请求函数的第一个参数就是包含 url/method/data 的标准 config 对象时,不需要额外配置 resolveArgs,guard 会自动识别。

用法二:只给单个请求加守护

项目中大部分请求不需要守护,只有某个特定请求函数需要保护(不区分 Axios 还是其他请求库):

javascript
import { setupRequestGuard } from '@hydd/request-guard';
import { submitOrder } from './api'; // 你的某个请求函数

// 只需要包裹这一个函数,最小化接入
const guardedSubmit = setupRequestGuard(submitOrder, {
  notify: (payload) => Toast.show(payload.message),
  rules: [{ method: 'post', duplicate: { strategy: 'block', message: '订单提交中' } }]
});

// 调用方式不变
const result = await guardedSubmit({ url: '/api/order', method: 'POST', data: formData });

自定义参数适配(非标准 config 参数)

当你的请求函数第一个参数不是标准 config 对象,或者函数有多个入参时(比如是 url 字符串、或多参数签名),可以通过适配函数说明参数如何映射:

钩子作用必须
resolveArgs(args)从函数入参中解析出标准 config 对象非标准签名时必须
mapConfig(config, args, context)将业务字段映射成 guard 标准字段(url/method/data/headers)字段名不一致时需要
getRequestGuard(config, args, context)从业务参数中提取 requestGuard 配置requestGuard 不在 config 顶层时需要
onBeforeRequest({ args, config, sourceConfig, context })真实请求发出前触发,可修改最终参数可选
onAfterResponse({ response, config, sourceConfig, context })请求成功后触发,可做统一业务处理可选
onAfterError({ error, config, sourceConfig, context })请求失败后触发,可做统一错误上报可选

场景一:函数签名为 request(url, config)

javascript
// 调用方式:guardedRequest('/api/submit', { method: 'POST', data: { id: 1 } })
const guardedRequest = setupRequestGuard(request, {
  resolveArgs(args) {
    const [url, config = {}] = args;
    return { args, config: { url, ...config } };
  }
});

场景二:多参数 SDK 签名 httpClient(method, url, data, options)

javascript
// 调用方式:guardedClient('POST', '/api/submit', { id: 1 }, { timeout: 5000 })
const guardedClient = setupRequestGuard(httpClient, {
  resolveArgs(args) {
    const [method, url, data, options = {}] = args;
    // configIndex: 3 — 把处理后的 config 回写到 args[3] 的位置
    return { args, config: { method, url, data, ...options }, configIndex: 3 };
  }
});

场景三:完整配置(所有钩子联合使用)

假设项目中有一个自定义 SDK,字段名非标准、requestGuard 配置放在特殊位置、需要统一注入 token 和错误上报:

javascript
import { setupRequestGuard } from '@hydd/request-guard';
import { sdkRequest } from './sdk';

// SDK 调用方式:sdkRequest({ api, type, payload, extra })
const guardedSdk = setupRequestGuard(sdkRequest, {
  notify: (payload) => Toast.show(payload.message),

  // 1. resolveArgs:第一个参数就是 config,不需要合并,直接返回
  resolveArgs(args) {
    return { args, config: args[0] };
  },

  // 2. mapConfig:字段名不标准,映射成 guard 能识别的 url/method/data/headers
  mapConfig(config) {
    return {
      url: config.api,
      method: (config.type || 'GET').toUpperCase(),
      data: config.payload,
      headers: config.extra?.headers
    };
  },

  // 3. getRequestGuard:requestGuard 配置在 config.extra.guard
  getRequestGuard(config) {
    return config.extra?.guard;
  },

  // 4. onBeforeRequest:请求发出前注入 token
  onBeforeRequest({ args, config, sourceConfig }) {
    const token = getToken();
    if (token && sourceConfig.extra) {
      sourceConfig.extra.headers = {
        ...sourceConfig.extra?.headers,
        Authorization: `Bearer ${token}`
      };
    }
  },

  // 5. onAfterResponse:请求成功后统一处理业务错误码
  onAfterResponse({ response, config }) {
    if (response?.code !== 0) {
      console.warn(`[业务异常] ${config.url}:`, response.message);
    }
  },

  // 6. onAfterError:请求失败后上报错误
  onAfterError({ error, config }) {
    reportError({ url: config.url, message: error.message });
  },

  rules: [
    { method: 'post', duplicate: { strategy: 'block', message: '操作处理中' } }
  ]
});

// 使用方式和原 SDK 完全一致
const result = await guardedSdk({
  api: '/user/bindPhone',
  type: 'POST',
  payload: { phone: '13800138000', code: '1234' },
  extra: {
    headers: { 'X-Source': 'app' },
    guard: { duplicate: { message: '正在绑定中...' } }  // 请求级守护配置
  }
});

基于 MIT 许可发布