Skip to content

5.1 响应模拟:状态码、响应体、响应头、延迟 ​

本章把"回信"这件事讲透。配合内嵌的场景参数实验室,所有效果都能立刻试。

状态码全家桶 ​

ts
HttpResponse.json({ id: 1 }, { status: 201 });            // 创建成功
new HttpResponse(null, { status: 204 });                  // 删除成功,无响应体
HttpResponse.json(body, { status: 400 });                 // 参数错误
HttpResponse.json(body, { status: 401, headers: {
  "WWW-Authenticate": "Bearer",
}});                                                       // 未认证
HttpResponse.json(body, { status: 403 });                 // 无权限
HttpResponse.json(body, { status: 404 });                 // 资源不存在
HttpResponse.json(body, { status: 409 });                 // 冲突
HttpResponse.json(body, { status: 422 });                 // 语义校验失败
HttpResponse.json(body, { status: 429, headers: {
  "Retry-After": "30",
}});                                                       // 限流
HttpResponse.json(body, { status: 500 });                 // 服务端内部错误
HttpResponse.json(body, { status: 503, headers: {
  "Retry-After": "60",
}});                                                       // 服务暂不可用

本教程提供一个"万能状态码端点",路径里的数字即返回码:

任意状态码:GET /api/status/:code

把路径改成 401、404、451、503 各试一次 —— 前端组件对状态码的分支逻辑就是这样测全的。

统一错误信封 ​

好的 mock 应该与真实后端约定一致。本教程所有错误都返回:

json
{ "error": { "code": "VALIDATION_FAILED", "message": "邮箱格式不正确", "details": {} } }
ts
function fail(status: number, code: string, message: string) {
  return HttpResponse.json({ error: { code, message } }, { status });
}

前端可以稳定地写 body.error.code 分支,切换到真实后端时无需改动。

响应头 ​

ts
const res = HttpResponse.json(pdfMeta, {
  headers: {
    "Content-Disposition": 'attachment; filename="report.pdf"',
    "X-Request-Id": requestId,
    "Cache-Control": "no-store",
  },
});

常见模拟目标:

响应头模拟场景
Location201 创建后指向新资源
WWW-Authenticate401 要求认证方案
Retry-After429 限流 / 503 维护
Content-Disposition文件下载名
Set-Cookie下发/清除会话 Cookie
X-Total-Count分页总数(无包壳风格 API)

各种响应体 ​

ts
// JSON(最常用)
HttpResponse.json(data);

// 文本
new HttpResponse("ok", { headers: { "Content-Type": "text/plain" } });

// HTML / XML
new HttpResponse("<html>维护中</html>", {
  headers: { "Content-Type": "text/html; charset=utf-8" },
});

// 二进制:用 Uint8Array / Blob / ArrayBuffer
const png = await createPngBytes();
new HttpResponse(png, { headers: { "Content-Type": "image/png" } });

// 空响应
new HttpResponse(null, { status: 204 });

// 网络失败(不是状态码,而是 fetch reject)
HttpResponse.error();

本站可直接体验二进制下载(浏览器会触发文件保存)与网络中断:

下载 SVG(观察 Content-Type / Content-Disposition)

网络失败:HttpResponse.error()

延迟模拟:100ms – 2000ms ​

慢接口是前端最常漏测的场景(loading 闪现、竞态、按钮连点)。MSW 有三种延迟写法:

ts
import { delay, HttpResponse } from "msw";

// 1) 精确延迟(毫秒)
await delay(1200);
return HttpResponse.json(data);

// 2) 拟真模式:按 MSW 内置的真实网络延迟分布随机等待
await delay();         // 等价于 await delay("real")
await delay("infinite"); // 永不返回(挂起请求,测试超时/取消逻辑)

// 3) 本教程做法:全局场景延迟,所有 handler 读取同一配置(100–2000 可调)
await delay(scenario.latencyMs);

v3 的 delay() 不再接收 { min, max } 对象:无参即拟真随机分布, 需要明确区间时(如本教程 100–2000ms 滑块)自行传入计算好的毫秒数。

下面的场景实验室与教程、两个示例共享同一份全局配置(存在 localStorage): 拖动滑块在 100–2000ms 之间调节,保存后点"探测"对比实际等待时间。

建议的测试矩阵:100ms(快网)→ 300ms(典型 4G)→ 2000ms(弱网), 分别检查 loading 态、防抖、请求竞态(旧响应覆盖新响应)与取消按钮。

错误状态注入 ​

场景实验室支持三种错误模式:

  • none:正常;
  • force:每个走 prelude() 的业务请求都返回 forceStatus(默认 500);
  • random:约 25% 概率注入错误,用于发现"偶发失败"下的 UI 韧性问题。

切到 force + 状态码 503,保存后回到 第四章的控制台 发请求,会稳定收到 503;切回 none 即恢复。 这个设计演示了一个重要思想:场景配置本身也可以是一组 mock 端点(GET/PUT /api/scenario),控制面板因此无需与 MSW 运行时直接耦合。

动态响应:同一端点,千变万化 ​

ts
http.get("*/api/users/:id", ({ params }) => {
  const user = db.users.find((u) => u.id === String(params.id));
  if (!user) return HttpResponse.json({ error: { code: "NOT_FOUND" } }, { status: 404 });
  return HttpResponse.json(user);
});

分页、搜索、回显请求头、根据 Accept 切换 JSON/XML —— 响应可以由请求的任何部分决定,下一章展开。

下载示例文件 ​