当第三方 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 上的演示已经运行了四年,没有任何问题。

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

相关推荐
手握风云-11 分钟前
JavaEE 进阶第四期:开启前端入门之旅(四)
前端
魔云连洲15 分钟前
React中的合成事件
前端·javascript·react.js
六月的可乐35 分钟前
【干货推荐】AI助理前端UI组件-悬浮球组件
前端·人工智能·ui
呼啦啦呼_40 分钟前
Echarts自定义地图显示区域,显示街道学校等区域,对原有区域拆分
前端
007php00744 分钟前
某大厂MySQL面试之SQL注入触点发现与SQLMap测试
数据库·python·sql·mysql·面试·职场和发展·golang
浩星1 小时前
iframe引入界面有el-date-picker日期框,点击出现闪退问题处理
前端·vue.js·elementui
技术钱1 小时前
element plus 多个form校验
前端
yume_sibai1 小时前
HTML HTML基础(3)
前端·html
米花丶1 小时前
JSBridge安全通信:iOS/Android桥对象差异与最佳实践
前端·webview
唐•苏凯2 小时前
ArcGIS Pro 遇到严重的应用程序错误而无法启动
开发语言·javascript·ecmascript