Nuxt中redirect跳转后,$axios.onError不会终止原页面加载,也就是原页面的asyncData会继续执行完,导致错误页面替换跳转页

1,350 阅读2分钟

当我们在首页获取一些数据时,但是这些数据请求(例如:"/cates/oneCategory"请求)需要token鉴权时,而我们的用户还没有鉴权时,我们的 @nuxt/axios 请求会得到401的错误

  async asyncData({ $axios }) {
    // 获取一级分类
    let { oneCategory } = await $axios.$get("/cates/oneCategory");
    
    // 对分类数据进行处理
    oneCategory = oneCategory.map((val) => ({
        ...val,
        text: val.categoryName,
      }));
    
    return {
      // 一级分类
      oneCategory,
    };
  },

虽然我们在下面的请求拦截器 $axios.onError 中 使用 redirect('/login') 跳转到了登录页 想让用户去鉴权,但是在地址栏已经到达 /login 后,nuxt默认的错误页面会显示 401错误页面 替换掉 登录页的页面内容

import { Toast } from "vant";
import { httpcode } from "./httpcode";

export default function ({ $axios, store, redirect }) {
  // 请求拦截器
  $axios.onRequest(() => {
    const token = store.state.token;
    // 如果存在token
    // 使用 @nuxt/axios提供的 setToken 设置 token
    token && $axios.setToken(token, "Bearer");
  });

  // 拦截到错误时
  $axios.onError(err => {
    // 根据http错误码 提示错误信息
    Toast.fail(httpcode[err.response.status])
    // 404
    if (err.response.status === 404) return redirect("404");
    // 未鉴权
    if (err.response.status === 401) {
      // 跳转登录页
      return redirect("/login")
      // 因为这里onError对于$axios只是一段异步代码,这里的终止 并不会终止 原来页面的asyncData等钩子函数的继续执行
    };
  })
}

因为这里onError对于$axios只是一段异步代码,这里的终止 并不会终止 原来页面的asyncData等钩子函数的继续执行 image.png 这样的结果是存在问题的,我们要正常显示 登录页才对。 所以,解决问题的关键思路来了:

1635254267940.png 通过 @nuxt/axios源码可以看出,onError 可以拿到 AxiosError 错误信息,通过调试可以发现,触发 错误页面是在 onError之后。

众所周知,$axiosPromise封装的。所以我们可以推断出$axios应该是在onError之后 Promise.reject(401错误信息),才显示nuxt错误页面的。

所以如果我们在这个之前 返回 Promise.resolve(), 401的错误就会被避开了,因为 await $axios.$get(...) 还没有等到Promise.reject(),就提前得到了我们给的 Promise.resolve()

但是 返回 Promise.resolve()$axios.$get(....) 会得到 undefined,

所以:

import { Toast } from "vant";
import { httpcode } from "./httpcode";

export default function ({ $axios, store, redirect }) {
  // 请求拦截器
  $axios.onRequest(() => {
    const token = store.state.token;
    // 如果存在token
    // 使用 @nuxt/axios提供的 setToken 设置 token
    token && $axios.setToken(token, "Bearer");
  });

  // 拦截到错误时
  $axios.onError(err => {
    // 提示错误信息
    Toast.fail(httpcode[err.response.status])
    // 404
    if (err.response.status === 404) return redirect("404");
    // 未鉴权
    if (err.response.status === 401) {
      // 跳转登录页
      redirect("/login")
      // 因为这里onError对于$axios只是一段异步执行,这里终止并不会终止 原本asyncData等钩子函数的执行

      // 为了解决跳转路由后还会显示错误页面的问题,
      // 在返回Promise.reject()之前返回Prmoise.resolve(),防止跳转到错误页

      // 因为$get()等方法是直接获取get()结果内部的data,所以,我们给出Promise.reject({data:{}}), 
      // 最起码保障让$get()拿到空对象{},以防止外界解构赋值时,再次跳转到错误页
      return Promise.resolve({ data: {} })
    };
  })
}

所以,解决的关键就是 这句 return Promise.resolve({ data: {} })

就像注释中说的,这里 resolve()的结果是先给了 $axios.get(), 是$axios.$get()封装前的方法,$get()拿到的是 get() 中的data对象,我们使用 $get(),所以 resolve({data:{}}),保障 $get() 拿到一个空对象 {},也保障外界可以解构赋值

不然,我们在使用asyncData等提前渲染的钩子中,后面还可能发生使用数据会遇到的错误

image.png

最后,我们要在每次获取数据时,判断结构出的数据是否是 undefined,然后使用。

  async asyncData({ $axios }) {
    // 获取一级分类
    let { oneCategory } = await $axios.$get("/cates/oneCategory");
    // 判断数据是否获取成功
    if (oneCategory)
      oneCategory = oneCategory.map((val) => ({
        id: val.id,
        text: val.categoryName,
      }));
      
    // 这里返回出去的oneCategory虽然是undefined,但是页面已经跳转,原页面模板不会解析,也就不会使用到 oneCategory,后面的也就不会有其它问题
    return {
      // 一级分类
      oneCategory,
    };
  },
  mounted() {
    console.log("可以发现,mounted不会执行");
  },

再尝试请求未鉴权的接口,登录页跳转后正常显示

image.png