• 封装 fetch 与 Error,返回 Promise 对象


    fetch 在目前已经是很成熟的请求资源的方法,但为了方便在项目中调用,一般都会进行二次封装

     

    一、定义错误类型

    对于封装公共组件或方法,一定要多想,七分设计,三分开发

    而对于一个网络请求来说,除了处理请求体、响应体之外,还有一个常常被忽略的环节,那就是定义 Error

    项目中关于网络请求的错误有很多种,比如超时错误、服务器错误、断网错误,这些都可以加以封装

    // errors.js
    
    export class ApiError extends Error {
      constructor(message, url) {
        super(message);
        this.message = message;
        this.name = 'ApiError';
        this.url = url;
      }
    }
    
    export class DisconnectError extends ApiError {
      constructor(url) {
        super('网络已断开,请重新连接', url);
        this.name = 'DisconnectError';
      }
    }
    
    export class ApiServerError extends ApiError {
      constructor(statusCode, url) {
        super(`请求服务器出错:${statusCode}`, url);
        this.name = 'ApiServerError';
        this.statusCode = statusCode;
      }
    }
    
    export class ApiJsonError extends ApiError {
      constructor(url) {
        super('请求服务器出错:无法转换为JSON', url);
        this.name = 'ApiJsonError';
      }
    }
    
    export class ApiTimeoutError extends ApiError {
      constructor(time, url) {
        super('请求超时', url);
        this.name = 'ApiTimeoutError';
        this.time = time;
      }
    }

    上面定义了几种常见的请求错误,其实还可以定义一种业务错误

    比如某个请求的状态是 200,但不符合后端定义的业务逻辑,返回了特殊的 code

    这时就可以根据后端返回的 code 进行业务错误的封装

     

    二、抛出错误

    在抛出上面定义的 Error 的时候,需要做一些判断,这部分逻辑可以抽出来

    // 检查网络状态是否已连接
    function checkonLine(url) {
      return new Promise((resolve, reject) => {
        if (!window.navigator.onLine) {
          reject(new DisconnectError(url));
        } else {
          resolve(url);
        }
      });
    }
    
    // 校验状态码
    function checkStatus(response) {
      const status = Number(response.status);
      if (status >= 200 && status < 300) {
        return response;
      }
      throw new ApiServerError(status, response.url);
    }
    
    // 解析 fetch 的响应结果
    function parseJSON(response) {
      return new Promise((resolve, reject) => {
        response
          .json()
          .then((json) => {
            // 记录请求的地址
            // eslint-disable-next-line
            json._SERVER_URL = response.url;
            resolve(json);
          })
          .catch((error) => {
            if (error instanceof SyntaxError) {
              reject(new ApiJsonError(response.url));
            } else {
              reject(error);
            }
          });
      });
    }

     

    三、封装 fetch

    准备就绪,可以上主菜了

    const FETCH_TIMEOUT = 1000 * 30;
    
    function request(path, params, options = {}) {
      const { body, method = 'GET', ...other } = params || {};
      const newMethod = `${method}`.toUpperCase();
      const newParams = { method: newMethod, credentials: 'include', ...other };
      const timeout = newParams.timeout || FETCH_TIMEOUT;
    
      let url = path;
    
      if (newMethod !== 'GET') {
        if (!(body instanceof FormData)) {
          newParams.headers = {
            Accept: 'application/json',
            'Content-Type': 'application/json; charset=utf-8',
            ...newParams.headers,
          };
          newParams.body = JSON.stringify(body);
        }
      } else {
        // 对GET请求增加时间戳 以避免IE缓存
        const timestamp = Date.now();
        const queryURL = qs.stringify({ ...body, t: timestamp });
        url = `${url}?${queryURL}`;
      }
    
      // 封装请求头
      newParams.headers = {
        ...newParams.headers,
        'Content-Type': 'application/json',
        'X-Requested-With': 'XMLHttpRequest',
        credentials: 'include',
      };
      return Promise.race([
        // 校验网络连接
        checkonLine(url)
          .then(() => {
            return fetch(url, newParams, options);
          })
          .then(checkStatus)
          .then(parseJSON)
          .catch((err) => {
            throw err;
          }),
        new Promise((resolve, reject) => {
          setTimeout(reject, timeout, new ApiTimeoutError(timeout, url));
        }),
      ]);
    }

    请求函数 request 已经搞定,这时可以简单的粗暴的 export 这个函数

    也可以导出具体的 get、post 方法

    export default {
      get: async (path, params, options) =>
        request(path, { method: 'GET', body: params }, options),
      post: async (path, params, options) =>
        request(path, { method: 'POST', body: params }, options),
      delete: async (path, params, options) =>
        request(path, { method: 'DELETE', body: params }, options),
      put: async (path, params, options) =>
        request(path, { method: 'PUT', body: params }, options),
    };

     

    四、更进一步

    上面的代码导出的是一个含有 get 等方法的对象,需要这么使用:

    import http from './request'
    
    http.get('/api/get', { name: 'wise' });
    http.post('/api/save', { name: 'wise' });

    不过对于 get 请求,很多的库做了进一步的封装,可以直接调用

    // 直接调用,默认使用 get 请求
    http('/api/get', { name: 'wise' });

    为了更好的体验,我们的代码也可以更进一步:

    const http = (url) => {
      return http.get(url);
    };
    
    http.get = async (path, params, options) =>
      request(path, { method: 'GET', body: params }, options);
    http.post = async (path, params, options) =>
      request(path, { method: 'POST', body: params }, options);
    http.delete = async (path, params, options) =>
      request(path, { method: 'DELETE', body: params }, options);
    http.put = async (path, params, options) =>
      request(path, { method: 'PUT', body: params }, options);
    
    export default http;

    搞定~

  • 相关阅读:
    新的nivida显卡安装时候出现unknown chipset
    cuda8和opencv3.1.0出现的问题
    es6-变量解构赋值
    web页面跳转的几种方式
    HTTP返回码中301与302的区别 (转载)
    EasyUI创建异步树形菜单和动态添加标签页tab
    Apache Rewrite匹配问号的问题
    Apache 启动.htaccess 的操作方法
    mysql、mysqli、PDO一句话概括比较
    maven的生命周期,和maven常用命令
  • 原文地址:https://www.cnblogs.com/wisewrong/p/15031654.html
Copyright © 2020-2023  润新知