惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog
Microsoft Security Blog
Microsoft Security Blog
Jina AI
Jina AI
博客园 - 叶小钗
J
Java Code Geeks
博客园 - 聂微东
博客园 - 司徒正美
大猫的无限游戏
大猫的无限游戏
阮一峰的网络日志
阮一峰的网络日志
V
V2EX
美团技术团队
WordPress大学
WordPress大学
M
MIT News - Artificial intelligence
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
GbyAI
GbyAI
罗磊的独立博客
T
The Blog of Author Tim Ferriss
aimingoo的专栏
aimingoo的专栏
T
Tailwind CSS Blog
The Cloudflare Blog
Stack Overflow Blog
Stack Overflow Blog
N
Netflix TechBlog - Medium
小众软件
小众软件

动物的光合作用

使用 Peer Relay 提高 Tailscale 的速度 内网穿透下 NAS 使用 DNS 轮循作为负载均衡 <众安车保>上当指南 - 动物的光合作用 JS 中的 Shebang/Hashbang - 动物的光合作用 我的自建 NAS - 动物的光合作用 React 组件中复用代码的方式 - 动物的光合作用 负载均衡下的前端资源更新策略 - 动物的光合作用 CSS 基于视区的长度单位 - 动物的光合作用 Excalidraw 支持自定义字体 - 动物的光合作用 我用 Docker 部署的一些服务 - 动物的光合作用 CSS 书写模式和逻辑属性 - 动物的光合作用 大量活动页面持续性维护的解决方案 - 动物的光合作用 Mac mini 搭建外网可访问的简易 NAS - 动物的光合作用 React Fragment 添加事件监听? - 动物的光合作用 如何在 React 解决竞态条件 - 动物的光合作用 使用脚本备份 Github 仓库 - 动物的光合作用 高性能 React 应用的几个小技巧 - 动物的光合作用 复习 DOM 事件 - 动物的光合作用 我的博客架构变迁之路 - 动物的光合作用 浏览器和 JavaScript 的一些新特性 - 动物的光合作用 useCallback 的误区 - 动物的光合作用 在 React 中使用事件分离状态 - 动物的光合作用 基于 Node.js 的 WebFont 解决方案
如何处理 RESTFUL 数据异常导致的前端错误 - 动物的光合作用
mebtte · 2019-05-30 · via 动物的光合作用

最近一段时间监控平台收集了很多 JS 错误,大部分都是TypeError

Uncaught TypeError: Cannot read property 'xxx' of null

经过排查后发现基本都是因为接口数据错误的导致,比如

// 正常数据
{
  "data": {
    "name": "xxx",
  },
}

// 错误数据
{
  "data": null,
}

data 应该是一个对象,如果返回 null 的话访问data.name就会产生TypeError。 至于接口为什么会返回错误的数据,原因有很多种

  • 接口本身 BUG
  • 手动修改数据库
  • 其他接口 BUG 导致错误地修改了数据

修复这些错误其实很简单,比如访问data之前先判断data是否为null

data ? data.name : '';
// 在其他的一些语言中有`?.`操作符可以很方便地进行上面的操作

但同时也会带来一些问题。

比如name是要显示在页面上,我的名字是xxx,我喜欢唱跳RAP和篮球,因为缺失了name,所以变成了我的名字是,我喜欢唱跳RAP和篮球。 这会对用户造成一些困扰。 在一些涉及到交易的场景可能会造成更大的麻烦。

另一个问题是如果接口数据复杂一点的话,为了避免报错你的页面可能会有很多判断的语句

{
  data: {
    user: { name: 'xxx' },
    theme: { color: 'red' },
  },
}

// 访问`user.name`
data ? (data.user ? data.user.name : '') : '';
// 或者
(data && data.user && data.user.name) || '';

// 访问`theme.color`
data ? (data.theme ? data.theme.color : '') : '';
// 或者
(data && data.theme && data.theme.color) || '';

对于这类问题,我的观点是数据异常的页面不应该展示给用户。所以,应该对接口的数据进行校验。

npm上有很多优秀的数据校验包,比如 ajvjoi 这些,以joi为例。

统一接口格式

{
  error_code: "错误码,正常为0",
  error_message: "错误信息,正常为''",
  data: "数据",
}

封装请求

// request.js
import axios from 'axios'; // 或 window.fetch

const request = async (options) => {
  const response = await axios(options);
  const {
    error_code: errorCode,
    error_message: errorMessage,
    data,
  } = response.data;
  if (errorCode !== 0) {
    throw new Error(`${errorCode}:${errorMessage}`);
  }

  // shcema 表示 joi 对象
  // 验证数据,部分接口可能不需要验证,所以 schema 是可选参数
  if (options.schema) {
    // validate 是 joi 对象自带的方法用于验证数据
    const { error } = schema.validate(data);

    // 数据验证失败
    if (error) {
      // 上报数据格式错误,对于一些简单的数据错误可以及时修改数据库
      reportDataError();
      throw error;
    }
  }

  return data;
};

export default request;

react为例,调用请求

import React, { useState, useEffect } from 'react';
import Joi from '@hapi/joi';

import request from './path/request';

// schema
const schema = Joi.object().keys({
  user: Joi.object().keys({
    name: Joi.string().required(),
  }),
  theme: Joi.object().keys({
    color: Joi.string().required(),
  }),
});

const Component = () => {
  const [status, setStatus] = useState('loading');
  const [data, setData] = useState(null);

  useEffect(() => {
    request({
      method: 'get',
      url: '/user',
      schema,
    })
      .then((user) => {
        setStatus('success');
        setUser(user);
      })
      .catch(() => setStatus('error'));
  }, []);

  if (status === 'success') {
    // 保证了可以正确地获取变量,渲染过程中不会报错
    return <div style={{ color: data.theme.color }}>{data.user.name}</duv>;
  }
  if (status === 'loading') {
    return <div>loading</div>;
  }
  return <div>error</div>;
};

当调用请求的时候,数据格式或数据出错可以显式地抛出错误让组件捕获,可以保证用户看到的页面一定是正确的,否则只会看到错误页面。 可以对请求进一步封装,如果多个组件调用就不需要重复定义schema

// getUserInfo.js
import Joi from 'joi';

import request from './path/request';

const schema = Joi.object().keys({
  user: Joi.object().keys({
    name: Joi.string().required(),
  }),
  theme: Joi.object().keys({
    color: Joi.string().required(),
  }),
});

export default () =>
  request({
    method: 'get',
    url: '/user',
    schema,
  });

当然,上面只是前端的一种预防方案,最重要应该还是在接口层面保证数据的完整性和正确性。