Next.js 实践总结 - 如何catch服务端请求错误

4,319 阅读5分钟

前言

最近用自己的脚手架做了几个项目,项目上线之后陆续在考虑优化相关的事情,并且想把一些自己使用Next.js的实践总结一个点一个点的抽离成解决方案。计划大概如下,先写哪个后写哪个就看准备完成程度了,我会在自己代码使用没问题之后总结发布:

  • 登录授权验证相关
  • 如何catch服务请求错误
  • nextjs中的错误处理和上报
  • ...其他相关优化总结

虽说是next.js的内容,不过解决方案脱离于代码层级,应该是通用的,所以无论你是next.js还是nuxt.js还是nest.js。应该都是可以的。

方案背景

【强调一下】: 这里catch的是服务端请求错误,而不是服务端运行时错误,运行时错误处理自己在server端进行处理,我说的是我们在服务端进行的请求出现错误了,前端如何知道并反馈。

客户端时的错误反馈

当客户端请求出现错误的时候,我们处理的应该是下面这样:

嗯,也就是所谓的反馈内容,特别是生产环境,线上用户反馈是必要的,如果出问题了,没获取到数据,还不给用户反馈,用户还以为就是没数据呢。

解决方案

服务端存在的问题

使用服务端渲染框架的小伙伴应该都知道,我们页面的初始化或者刷新的时候部分数据是从服务端获取的,那么服务端获取数据如果失败了应该如何捕获反馈出来给用户查看呢?其实不仅仅是反馈给用户,服务端的数据请求失败与否开发人员在调试的时候也只能通过node控制台去查看,如果给一些提示在浏览器,也会帮助开发人员~

服务端获取数据请求是不会在浏览器显示出来,因此,错误也不会被我们的浏览器打印或提示。因为node端并没有windowdocumentalert等这些弹窗变量,那么该如何做呢?

方案思路

我们知道node端可以捕获错误,但是无法在浏览器弹出,那么显而易见,方案就是node端的错误保存下来,当浏览器环境的时候把这些错误弹出即可!

为什么可行?

首先,SSR框架的特点所致,它并不是所有请求都是服务端获取,服务端获取的场景一般两种情况:第一种,系统初始化进入;第二种,任意页面的刷新。只有上面两种前提,并且我们还在getInitialProps这个方法进行了数据获取,才走的是服务端,也就是说我们捕获的错误场景也就是上面两种场景的服务端数据获取。

其次,因为上面的情况,我们可以确定,我们只需要在_app.jscomponentDidMount的生命周期将捕获到的错误弹出即可,因为该周期就确定页面已经在浏览器加载完成了。

如何捕获node端的请求错误再从浏览器端取出

Redux State。嗯,答案应该不意外吧,肯定得是两端都存在都能用才可以,那肯定是redux了,我们在node捕获错误,然后存入state,然后在componentDidMount取出来进行提示。

那么global行不行呢? 不行,global是node级别的全局变量,是整个系统共享的,我们如果修改的话,在高并发的场景下会出现错乱等情况。

第一步 - redux state

我们新建一个serverError state,然后里面存放的是我们即将在store捕获到的错误,我这里就简单的存放的是错误类型,至于你们想怎么提示,根据自己的需求来。

import {
  SERVER_ERROR,
  CLEAR_SERVER_ERROR
} from '../../constants/ActionTypes';

const initialState = {
  errorType: []
};

const serverError = (state = initialState, { type, payload }) => {
  switch (type) {
    case SERVER_ERROR: {
      const { errorType } = state;
      errorType.includes(payload) ? null : errorType.push(payload);
      return {
        ...state,
        errorType
      };
    }
    case CLEAR_SERVER_ERROR: {
      return initialState;
    }
    default:
      return state;
  }
};

export default serverError;

第二步 - redux action

接下来,我们来捕获服务端的错误,我这边一般使用下面这种形式定义actions

export const FETCH_XXX = 'FETCH_XXX'; // 请求action
export const FETCH_XXX_FAIL = 'FETCH_XXX_SUCCESS'; // 请求成功action
export const FETCH_XXX_FAIL = 'FETCH_XXX_FAIL'; // 请求失败action

那么也就是说,我们在redux里要捕获的是server端的XXX_FAIL的action,这些都是服务端发生了的错误请求。

// middleware.js 

import {
  SERVER_ERROR
} from '../../constants/ActionTypes';

export default () => next => action => {
  if (!process.browser && action.type.includes('FAIL')) {
    next({
      type: SERVER_ERROR,
      payload: action.type 
    });
  }
  return next(action);
};

第三步 - 浏览器端处理

上面我们就把node端的错误请求存到state里了,接下来要做的就是在浏览器端取出弹窗提示给用户就行了。

// _app.js
...
componentDidMount() {
    const { store: { getState, dispatch } } = this.props;
    // 处理服务器端的错误提示
    const { errorType } = getState().serverError;
    if (errorType.length > 0) {
      Promise.all(
        errorType.map(type => message.error(`服务器错误, 代码:${type}`))
      );
      dispatch(clearServerError());
    }
  }
...

接下来说一下,为什么要存放的是数组,然后前端为什么循环弹出。因为我们在服务端发的请求不一定只有一个,有可能一个页面有三个请求同时在服务端获取,我们如果不用数组,在他们都失败的情况下,我们只能捕获到最后一个失败的请求,显然这样是不够的,我们需要捕获所有的失败请求并反馈~所以最后的方案就是上面的那种,效果如下:

为了给大家区分,我特意加了type,浏览器端只有错误提示,服务器端加了错误的type。具体场景大家可以根据自己的想法来~

总结

这个算是开发过程中的一个思考或者说是优化吧,因为服务端错误如何捕获这个问题很明显存在于Next/Nuxt/Nest等各种SSR框架中,我查了应该没有太好的解决办法,除非你直接上sentry这种牛逼的监控服务。我这里也只是基于自己的思考给出的一种方案,希望能给大家提供一些思路~

【延伸】 比如你不仅仅是做错误提示,还有上报或者其他事情,我觉得应该也可以通过这种方式将信息携带过来,然后再处理~

最后,代码示例在我的脚手架next-antd-scaffold_server-error分支,感兴趣的可以查看,star/fork都万分感谢~

Next.js,如果有更多问题或者想法,可以一起交流: