停止你错误的请求封装

自从上次写了一篇《如何正确处理 promise、await 产生的错误》之后,有很多童鞋问我,如何正确的封装请求,以及如何处理请求的错误。今天就来聊聊这个问题。

本文以 axios 为例,以一般网络上的封装为例,来说明例子中的问题。

一个错误的封装

我们假设 api 的地址地址为:api.my.com 且返回的数据格式为:

typescript 复制代码
interface ResponseBody<T> {
  code: number;
  data: T;
  message: string;
}

我们假设 code 为 0 时,表示请求成功,否则表示请求失败。 并且假设引入 element-ui 的 Message 组件,用于显示错误信息。

typescript 复制代码
import axios from "axios";
import { Message } from "element-ui";

const request = axios.create({
  baseURL: "https://api.my.com",
});

request.interceptors.response.use(
  (response) => {
    const responseBody = response.data;
    if (responseBody.code === 0) {
      return responseBody.data;
    } else {
      Message.errror(responseBody.message);
    }
  },
  (error) => {
    Message.error(error.message);
  }
);

export default request;

上述代码,我们在其他地方使用的时候,就可以这样使用:

typescript 复制代码
import request from "./request";

async function getUserInfo() {
  const userInfo = await request.get("/user/info");
  console.log(userInfo);
}

问题

上述代码,看起来没有什么问题,但是,实际上,这个封装是有问题的。

问题在于,我们在 request 中,对于错误的处理,是通过 Message.error 来处理的。这样的话,就会导致,我们在使用 request 的时候,无法捕获错误。在后续的代码中,我们会得到一个 undefined 的结果。

这种情况,在《如何正确处理 promise、await 产生的错误》中,已经提到过了。我叫这种情况为:错误转移

因为我们由于错误的处理,导致错误信息与堆栈丢失,从而导致错误的转移。这种情况,是非常严重的,因为我们无法定位错误的位置,也无法定位错误的原因。

具体分析,我们可以查看《如何正确处理 promise、await 产生的错误》中的分析。

另一个小问题是,对于axios里边,get和post等方法,返回的是一个 Promise<Response<T>>, 而不是一个 Promise<T>。 如果我们想类型推导正确。我们最好把 response 返回,而不是 response.data。 当然,我们可以覆写 axios 的类型,但是这个问题,就不在本文的讨论范围内了。

正确的封装

我们需要把错误转移的问题,交给调用者来处理。我们只需要把错误抛出即可。

typescript 复制代码
request.interceptors.response.use(
  (response) => {
    const responseBody = response.data;
    if (responseBody.code === 0) {
      response.data = responseBody.data;
      return response;
    } else {
      throw new Error(responseBody.message);
      // 或者
      // return Promise.reject(new Error(responseBody.message));
    }
  },
  (error) => {
    throw error;
    // 或者
    // return Promise.reject(error);
  }
);

这种情况下,错误可以在上层调用者中捕获到。一般情况下,我们会在最顶层的调用者中捕获错误,而不是在每一个调用者中捕获错误。 其中在浏览器中,我们可以在程序入口中捕获,或者对于未捕获的错误,我们可以通过 window.onunhandledrejection 来捕获。

其实一些主流请求库中,拦截器的作用,是为了将程序正确的流程,与错误的流程分开。让服务端返回的,应该表达出来的错误,能够正确的表达出来。而不是用于错误处理。

在程序入口中捕获

在不同的框架中,程序入口的位置不同。在 vue 中,我们可以在 errorHandler 中捕获错误, 对于 vue-router 中的错误,我们可以在 router.onError 中捕获错误。

typescript 复制代码
import Vue from "vue";
import router from "./router";

Vue.config.errorHandler = (err, vm, info) => {
  // 用于开发者工具的错误提示
  console.error(err);
  // 用于用户的错误提示
  Message.errror(responseBody.message);
};

router.onError((err) => {
  console.error(err);
  Message.errror(responseBody.message);
});

通过 onunhandledrejection 捕获

以下例子是通过 window.onunhandledrejection 来捕获错误:

typescript 复制代码
window.onunhandledrejection = (event) => {
  console.error(event.reason);
};

如果在 nodejs 中,我们可以通过 process.on('unhandledRejection', (reason, promise) => {}) 来捕获。

错误的可能性

在上述例子中,如果我们的 api 返回错误,我们可能得到以下几种错误:

  • 在错误拦截器中,我们主动抛出的错误 throw new Error(responseBody.message);
  • axios中,axios自动抛出的错误 new AxiosError(message, config, code, request, response);,这种错误,在状态码异常的时候,会自动抛出。具体可以查看axios的源码。

错误处理器

在《如何正确处理 promise、await 产生的错误》中,我们提到了错误处理器的概念。错误处理器,是一个函数,它接受一个错误,然后对错误进行处理。

在上述的例子中,我们可以看到,我们在最顶层的调用者中捕获错误,console.error 充当了我们最简单的错误处理器。

但是,如果我们想要更加复杂的错误处理,我们可以进一步封装。如果童鞋们感兴趣,我们可以在下一篇文章中,来聊聊错误处理器的封装。

总结

本文,我们聊了聊,如何正确的封装请求,以及如何处理请求的错误。总结起来,就是:

  • 不要在拦截器中,对错误进行处理(包括log,toast等),而是应该抛出错误。
  • 在最顶层的调用者中,捕获错误。

还是引用那就话

我不喜欢别人将await/proimise跟错误处理捆在一起讲,这显得很菜。

同理,我也不喜欢别人将请求封装跟错误处理捆在一起讲,因为请求错误只是错误的一种,而我们的错误处理器,应该是一个通用的错误处理器,而不是针对某一种错误的处理器。

相关推荐
Avan_菜菜1 小时前
AI 能写代码了,为什么我反而开始要求它先写文档?
前端·github·ai编程
爱勇宝6 小时前
鸿蒙生态的下半场:开发者不只要能开发,还要能赚钱
android·前端·程序员
IT_陈寒9 小时前
SpringBoot这个自动配置坑我跳了三次
前端·人工智能·后端
kyriewen9 小时前
我用 AI 一周写完了整个项目,上线第一天就崩了——这是我踩过最贵的 5 个坑
前端·javascript·ai编程
牧艺9 小时前
从零到协同:构建类飞书在线文档系统的五个技术重难点
前端·人工智能
红尘散仙10 小时前
想写一个像样的终端 App?试试把 React 的开发体验搬进 Rust TUI
前端·rust
袋鼠云数栈UED团队11 小时前
一套 Spec-First 的 AI 编程工作流
前端·人工智能
袋鼠云数栈前端11 小时前
一套 Spec-First 的 AI 编程工作流
前端·ai+
angerdream11 小时前
Android手把手编写儿童手机远程监控App之vue3 路由守卫
前端
不服老的小黑哥11 小时前
AI规范驱动编程-harness工程项目实战
前端