当第三方 API 失效时,如何保留文章演示示例

本篇依然来自于我们的 《前端周刊》 项目!

由团队成员 田八 翻译,欢迎大家 进群 持续追踪全球最新前端资讯!!

原文地址:css-tricks

四年后,我的"使用 WordPress REST API 进行无头表单提交"文章中的示例最终停止运行了。

这篇文章中包含了 CodePen 的嵌入,演示了如何使用流行的 WordPress 表单插件的 REST API 端点来捕获和显示验证错误和提交反馈,从而构建一个完全自定义的前端。这些代码演示依赖于我在后台运行的一个 WordPress 网站。但是在一次被迫的基础设施迁移过程中,该网站没能正确的被转移,更糟糕的是,我失去了对我的账户的访问权限。

当然,原本我是可以联系技术支持或者在其他地方恢复备份。但这种情况让我产生怀疑:如果这不是 WordPress 呢?如果它是一个我无法自托管或修复的第三方服务呢?有没有办法构建不会因为依赖的服务失败而中断的演示?我们如何确保教学示例尽可能长时间可用?

还是说这些是不可避免的?示例是否像网络上的其他一切一样,注定会停止运转?

与软件测试的相似之处

对于编写代码测试的人来说,长期以来他们一直都在与类似的问题作斗争,尽管表述方式有所不同。但核心问题是相同的。依赖关系,尤其是第三方依赖关系成为障碍,因为它们超出了控制范围。

毫不奇怪,消除外部依赖问题最靠谱的方法是将外部服务完全移除,从而有效地与其解耦。当然,怎么做到这一点,以及是否每次都是可行的,需要视具体情况而定。

事实上,在使示例能够快速还原这方面,处理依赖关系的技术同样有用。

为了更具体说明,我会使用前面提到的 CodePen 演示作为示例。同样的方法在很多其他的情况下也同样有效。

解耦 REST API 依赖关系

尽管有很多策略和技巧,但打破对 REST API 的依赖的两种最常见方法是:

  1. 在代码中模拟 HTTP 调用,不是执行真正的网络请求,而是返回备份的响应体
  2. 使用模拟 API 服务器作为真实服务的替代品,并以类似的方式提供预定义的响应

两者都有利弊,我们稍后再讨论。

使用拦截器模拟响应

在现代测试框架中,无论是用于单元测试还是端到端测试(例如JestPlaywright),都提供内置的Mock功能。

然而,我们并不一定需要这些,反正我们也不能在 CodePen 中使用它们。相反,我们可以对Fetch API进行 monkey patch 来拦截请求并返回模拟响应。当无法更改原始源代码时使用 monkey patch,我们可以通过覆盖现有函数来引入新的行为。

它实现起来如下:

javascript 复制代码
const fetchWPFormsRestApiInterceptor = (fetch) => async (
  resource,
  options = {}
) => {
  // 确保我们处理的是预期的数据
  if (typeof resource !== "string" || !(options.body instanceof FormData)) {
    return fetch(resource, options);
  }

  if (resource.match(/wp-json/contact-form-7/)) {
    return contactForm7Response(options.body);
  }

  if (resource.match(/wp-json/gf/)) {
    return gravityFormsResponse(options.body);
  }

  return fetch(resource, options);
};

window.fetch = fetchWPFormsRestApiInterceptor(window.fetch);

我们用自己的版本覆盖默认的fetch,该版本在特定条件添加了自定义逻辑,否则保持原有的行为不变。

替换函数fetchWPFormsRestApiInterceptor的作用类似于拦截器。拦截器是一种根据特定条件修改请求或响应的设计模式。

许多 HTTP 库,例如曾经风靡一时的axios,都提供了一个方便的 API 来添加拦截器,而无需依赖 monkey patching。在管理多次重写覆盖时,很容易无意中产生细微的 bug 或引发冲突,因此应该谨慎使用monkey patching

有了拦截器,返回Mock响应就像调用response对象的静态JSON方法一样简单:

ini 复制代码
const contactForm7Response = (formData) => {
  const body = {}

  return Response.json(body);
};

根据需要,响应可以是纯文本BlobArrayBuffer的任何内容。还可以指定自定义状态代码,并包含其他响应标头。

对于 CodePen 的演示,构建的响应体的结构可能如下:

php 复制代码
const contactForm7Response = (formData) => {
  const submissionSuccess = {
    into: "#",
    status: "mail_sent",
    message: "Thank you for your message. It has been sent.!",
    posted_data_hash: "d52f9f9de995287195409fe6dcde0c50"
  };
  const submissionValidationFailed = {
    into: "#",
    status: "validation_failed",
    message:
      "One or more fields have an error. Please check and try again.",
    posted_data_hash: "",
    invalid_fields: []
  };

  if (!formData.get("somebodys-name")) {
    submissionValidationFailed.invalid_fields.push({
      into: "span.wpcf7-form-control-wrap.somebodys-name",
      message: "This field is required.",
      idref: null,
      error_id: "-ve-somebodys-name"
    });
  }

  // 或者以一种更彻底的方式检查电子邮件地址的有效性 
  if (!/^[^\s@]+@[^\s@]+.[^\s@]+$/.test(formData.get("any-email"))) {
    submissionValidationFailed.invalid_fields.push({
      into: "span.wpcf7-form-control-wrap.any-email",
      message: "The email address entered is invalid.",
      idref: null,
      error_id: "-ve-any-email"
    });
  }

  // 返回成功或验证失败的响应
  const body = !submissionValidationFailed.invalid_fields.length
    ? submissionSuccess
    : submissionValidationFailed;

  return Response.json(body);
};

此时,任何使用fetch调用的URL如果包含wp-json/contact-form-7,都会返回Mock的成功或验证错误响应,具体取决于表单输入。

现在让我们将其与 Mock API 服务方法进行对比。

使用serverlessMock API 服务

运行传统托管模式的 Mock API 服务器,会重新带来可用性、维护和成本方面的问题。尽管围绕serverless功能的炒作已逐渐平息,但我们可以使用它们来规避这些问题。

而且由于DigitalOcean Functions慷慨的提供了免费套餐,创建 Mock API 实际上是免费的,只需要手动模拟即可。

对于简单的用例,所有操作都可以通过 Functions 控制面板完成,包括在内置编辑器中编写代码。观看这段简洁的演示视频,了解实际操作:

YouTuBe

视频地址在当前平台不支持预览,请点击链接查看。

对于更复杂的需求,Functions可以在本地开发,并使用 doctl(DigitalOcean 的 CLI)进行部署

为了返回Mock响应,若我们为每个端点单独创建一个Function,这样操作会更简单,因为这样能避免添加不必要的条件判断。幸运的是,我们可以继续使用 JavaScript(Node.js),并且和 contactForm7Response 几乎相同的基础配置开始:

csharp 复制代码
function main(event) {
  const body = {};

  return { body };
}

我们必须将处理函数命名为 main,它会在端点被调用时触发。该函数的第一个参数是event对象,其中包含请求的详细信息。同样,我们可以返回任何内容,如果需要返回我们需要的 JSON 响应,只需直接返回一个对象即可。

我们可以复用创建响应的相同代码。唯一的区别在于,需要我们自己从event对象中提取表单输入数据:

kotlin 复制代码
function main(event) {
  // 如何从 event 中获取 FormData?
  const formData = new FormData();

  const submissionSuccess = {
    // ...
  };
  const submissionValidationFailed = {
    // ...
  };

  if (!formData.get("somebodys-name")) {
    submissionValidationFailed.invalid_fields.push({
      // ...
    });
  }

  // 或者以一种更彻底的方式检查电子邮件地址的有效性 
  if (!/^[^\s@]+@[^\s@]+.[^\s@]+$/.test(formData.get("any-email"))) {
    submissionValidationFailed.invalid_fields.push({
      // ...
    });
  }

  // 返回成功或验证失败的响应
  const body = !submissionValidationFailed.invalid_fields.length
    ? submissionSuccess
    : submissionValidationFailed;

  return { body };
}

至于数据转换,serverless函数通常期望接收 JSON 格式的输入,因此对于其他数据类型,需要额外的解析步骤。值得一提的是 CodePen 演示中的表单提交时使用的是 multipart/form-data

无需任何库,我们可以利用Response API的功能将multipart/form-data字符串转换为FormData

javascript 复制代码
async function convertMultipartFormDataToFormData(data) {
  const matches = data.match(/^\s*--(\S+)/);

  if (!matches) {
    return new FormData();
  }

  const boundary = matches[1];

  return new Response(data, {
    headers: {
      "Content-Type": `multipart/form-data; boundary=${boundary}`
    }
  }).formData();
}

这段代码的主要侧重于提取boundary变量。这通常是自动生成的,例如在浏览器中提交表单时。

提交的原始数据可以通过event.http.body获得,但由于它是base64编码的,我们需要先对其进行解码:

csharp 复制代码
async function main(event) {
  const formData = await convertMultipartFormDataToFormData(
    Buffer.from(event?.http?.body ?? "", "base64").toString("utf8")
  );

  // ...

  const body = !submissionValidationFailed.invalid_fields.length
    ? submissionSuccess
    : submissionValidationFailed;

  return { body };
}

就这样,采用这种方法后,剩下的只需将对原始API的调用替换为对Mock API的调用即可。

总结

最终,这两种方法都有助于将演示示例与第三方API依赖解耦。从工作量来看,至少在这个具体案例中,两者几乎相差无几。

手动模拟方法的优势在于几乎没有任何外部依赖,甚至没有我们能控制的依赖,所有功能都集成在项目内部。总体而言,在不了解具体细节的情况下,对于小型、独立的演示示例,我们完全有理由优先选择这种方法。

但使用 Mock API服务也有其优势。Mock服务不仅能支持演示,还能支持各类测试需求。对于更复杂的场景,专门负责Mock服务的团队可能更喜欢使用 JavaScript 以外的编程语言,或是直接采用WireMock等工具,而非从头搭建。

就像所有事情一样,这取决于具体需求。除了前文提到的因素,还有许多其他标准需要权衡。

此外,我也不认为这种方法必须默认采用。毕竟,我 CodePen 上的演示已经运行了四年,没有任何问题。

关键在于,我们需要建立一套机制来监测演示示例何时失效(监控),并在失效发生时,我们有合适的工具来快速处理这种情况。

相关推荐
用户527096487449014 分钟前
SCSS模块系统详解:@import、@use、@forward 深度解析
前端
兮漫天14 分钟前
bun + vite7 的结合,孕育的 Robot Admin 【靓仔出道】(十一)
前端·vue.js
xianxin_15 分钟前
CSS Text(文本)
前端
秋天的一阵风16 分钟前
😈 藏在对象里的 “无限套娃”?教你一眼识破循环引用诡计!
前端·javascript·面试
电商API大数据接口开发Cris20 分钟前
API 接口接入与开发演示:教你搭建淘宝商品实时数据监控
前端·数据挖掘·api
用户14095081128021 分钟前
原型链、闭包、事件循环等概念,通过手写代码题验证理解深度
前端·javascript
汪子熙21 分钟前
错误消息 Could not find Nx modules in this workspace 的解决办法
前端·javascript
前端美少女战士23 分钟前
post方法下载文件,需做哪些特殊处理
javascript·react.js
小悟空28 分钟前
【AI生成+补充】高频 hql的面试问题 以及 具体sql
sql·面试·职场和发展
skeletron201136 分钟前
🚀AI评测这么玩(2)——使用开源评测引擎eval-engine实现问答相似度评估
前端·后端