在 NextJS 预获取数据时正确处理异常请求

2 年前(已编辑)
编程 /
2209
6
这篇文章上次修改于 2 年前,可能部分内容已经不适用,如有疑问可询问作者。

在用 NextJS 构建 SSR 应用的时候总是要在页面的 getInitialProps 方法中预先获取数据,然后由框架注水,以便在 hydate 时候不用再由浏览器端去加载重复数据。但是在这一个步骤中请求可能会发生错误。如果请求的接口应该是由服务端给出的 404,但是在这个步骤中请求抛出错误之后,NextJS 会直接捕获并给错误页,状态却是 500,这明显不是我们预期的结果,期望得到的是请求状态 404。由于 NextJS 不知道如何处理异常,所以默认状态码是 500,那么如何解决,转发出原请求的状态码呢。

首先模拟一个 404 请求。以下由 express 为例。

const router = express.Router()

router.get('/data', (req, res) => {
  res.status(200).send({ data: 'Hello World!' })
})

router.get('/404', (req, res) => {
  res.status(404).send({ message: '页面走丢了' })
})

router.get('/403', (req, res) => {
  res.status(403).send({ message: '这..不能看!' })
})

server.use('/mock', router)

/mock 路由下挂载 3 个路由,分别是 mock 404,403,200 的路由。新建一个 NextPage,去获取 404 的数据。如下:

// pages/get-404.js
import { $axios } from '../utils/axios'

const _404 = (props) => {
  return <pre>{props.data}</pre>
}

_404.getInitialProps = async () => {
  const { data } = await $axios.get('/mock/404')
  return data
}

export default _404

在开发模式下,NextJS 会直接把异常抛出而不会进入 Error 页,所以需要先 build 然后在生产环境下启动 NextJS。在生产环境下访问这个路由会出现如下页面。

Error 页显示 500,请求 /get-404 也是 500,而我们想要的是 404。

根据文档所说,我们可以新建 _Error.js 去处理异常,官网给的案例如下:

// pages/_error.js
import ErrorPage from 'next/error'

ErrorPage.getInitialProps = async ({ res, err }) => {
  const statusCode = res ? res.statusCode : err ? err.statusCode : 404
  return { statusCode }
}

export default ErrorPage

这样并不能很好的解决问题。接下来我们来修改一下 axios 的异常拦截器。

const axios = require('axios')

const $axios = axios.default.create({
  baseURL: 'http://127.0.0.1:3000',
})

$axios.interceptors.response.use(undefined, (err) => {
  const response = err.response
  const status = response ? response.status : 408
  return Promise.reject(new RequestError(status, response))
})

export { $axios }

class RequestError extends Error {
  constructor(status, response) {
    const message = response
      ? response.data?.message || 'Unknown Error'
      : 'Request timeout'
    super(message)
    this.status = status
    this.response = response
  }
}

在拦截器中把异常进行处理,并由自定义异常类再次抛出。在这个类中,我们可以预先获取到 HTTP 异常码,注意:如果是网络中断问题,statusECONNREFUSED,需要在额外判断。

然后在 _error.js 中处理。由于默认的 ErrorPage 不支持自定义所以稍作修改 UI。展示后端异常中的 message

// @ts-check

import { RequestError } from '../utils/axios'
import styles from './error.module.css'
const MyErrorPage = ({ message, statusCode }) => {
  return (
    <div className={styles['error']}>
      <div>
        <h1>{statusCode}</h1>
        <div className={styles['desc']}>{message}</div>
      </div>
    </div>
  )
}
MyErrorPage.getInitialProps = async ({ res, err }) => {
  if (err instanceof RequestError) {
    const { status, message } = err

    res && (res.statusCode = typeof status === 'number' ? status : 408)
    return {
      statusCode: status,
      message,
    }
  }

  return { statusCode: res.statusCode, message: '' }
}

export default MyErrorPage

在 ErrorPage 的 getInitialProps 中处理异常,如果是预先定义的 RequestError 的话,就是请求异常了,直接获取到异常中的 message,statuscode 字段直接返回就好了。同时可以修改 res.statusCode 覆盖原有的 500 状态码,从而达到转发请求异常状态码。重新 build 后,如下图。

已实现预期,同时获取页面时状态码也是 404。好耶。403 也是可以的。

完整 Demo:https://github.com/Innei/nextjs-request-error-demo

评论区加载中...