停止你错误的请求封装

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

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

相关推荐
喵叔哟12 分钟前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特1 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解1 小时前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
丶21361 小时前
【WEB】深入理解 CORS(跨域资源共享):原理、配置与常见问题
前端·架构·web
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
Mr.咕咕1 小时前
Django 搭建数据管理web——商品管理
前端·python·django