停止你错误的请求封装

自从上次写了一篇《如何正确处理 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跟错误处理捆在一起讲,这显得很菜。

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

相关推荐
VT.馒头1 分钟前
【力扣】2637. 有时间限制的 Promise 对象
前端·javascript·leetcode·typescript
zhengxianyi5152 分钟前
Vue2 打包部署后通过修改配置文件修改全局变量——实时生效
前端·vue.js·前后端分离·数据大屏·ruoyi-vue-pro
灵犀坠3 分钟前
Vue3 实现音乐播放器歌词功能:解析、匹配、滚动一站式教程
开发语言·前端·javascript·vue.js
north_eagle4 分钟前
ReAct 框架详解
前端·react.js·前端框架
OEC小胖胖6 分钟前
13|React Server Components(RSC)在仓库中的落点与边界
前端·react.js·前端框架·react·开源库
OEC小胖胖8 分钟前
14|Hook 的实现视角:从 API 到 Fiber Update Queue 的连接点
前端·react.js·前端框架·react·开源库
军军君0110 分钟前
Three.js基础功能学习十:渲染器与辅助对象
开发语言·前端·javascript·学习·3d·前端框架·ecmascript
Marshmallowc11 分钟前
React useState 数组 push/splice 后页面不刷新?深度解析状态被『蹭』出来的影子更新陷阱
前端·react.js·前端框架
GIS之路15 分钟前
ArcGIS Pro 添加底图的方式
前端·数据库·python·arcgis·信息可视化
Mo_jon17 分钟前
vite + vue 快速构建 html 页面 (舒适编写html文件)
前端·vue.js·html