ES6 + 实战 2 - 封装请求

1. 前言

在学习 Promise 相关小节时我们已经了解了 Promise 的基本用法和 Promise A + 规范,那么在实际项目中我们应该怎么去使用 Promise 来提高我们的效率,并且可以通过 Promise 去封装一些异步方法,让我们在使用过程中更加得心应手。

本节我们将模拟一个真实的生产环境来对前端开发中最常见也是最重要的数据请求进行封装。通过使用封装 Promise 请求来学习 Promise 在实际项目当中是如何使用的。

2. 环境搭建

工欲善其事,必先利其器,在我们进入本节的学习前,我们需要先搭建我们的开发环境,在实际的项目中也是必须的。本节使用的是 Vue 脚手架生成的项目,不了解 Vue 的同学可以先去学习一下。在 vue.config.js 配置中,对 ajax 请求进行了 mock 操作,mock 的逻辑在 mock.config.js 文件中,mock 的结果在 mock 文件夹下对应的 json。

这样的配置在本节中就可以基本模拟真实的数据请求过程了,本节的源码在 GitHub 上。

3. 封装 ajax 请求

ajax 是前端用于发送接口请求的技术,它是异步的,需要等待结果返回后执行

在发送 ajax 请求时,我们可能会这样去写。

ajax({
  url: '',
  method: '',
  data: {},
  params: {},
  success: function (res) {},
  error: function (err) {}
})
  • url: 接口请求地址;
  • method: 接口请求方法,如:get、post 等;
  • data: 请求时使用 body 传输的数据,一般用于 post 请求中;
  • params: 请求时使用 url 传递的数据,一般用于 get 请求中;
  • success: 接口请求成功时的回调,参数为接口成功的返回值;
  • error: 接口请求失败时的回调,参数为抛出异常时的调用栈等信息。

XMLHttpRequest 是浏览器提供的对象,用于进行后台与服务端的数据进行交互

实现 ajax

function ajax(options) {
  const { url, method, data, params, success, error } = options;
  const xhr = new XMLHttpRequest();

  xhr.onreadystatechange = function () {
    // readyState为4的时候已接收完毕
    if (xhr.readyState === 4) {
      // 状态码200表示成功
      if (xhr.status === 200) {
        console.log(xhr.responseText);
        success.call(this, xhr.responseText);
      } else {
        console.log(xhr.status);
        error.call(this, xhr.status)
      }
    }
  };

  // get 请求
  if (method === 'get' || method === 'GET') {
    if (typeof params === 'object') {
      // params拆解成字符串
      params = Object.keys(params).map(function (key) {
        return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
      }).join('&');
    }
    url = params ? `${url}?${params}` : url;
    xhr.open(method, url, true);
    xhr.send();
  }

  // post 请求
  if (method === 'post' || method === 'POST') {
    xhr.open(method, url, true);
    xhr.setRequestHeader("Content-type", "application/json; charset=utf-8");
    xhr.send(JSON.stringify(params));
  }
}

使用 promise 进行封装

function ajax(url, method, params) {
  return new Promise((resolve, reject) => {
    // 创建XMLHttpRequest对象
    const xhr = new XMLHttpRequest();
    // 状态改变时的回调
    xhr.onreadystatechange = function () {
      // readyState为4的时候已接收完毕
      if (xhr.readyState === 4) {
        // 状态码200表示成功
        if (xhr.status === 200) {
          resolve(xhr.responseText);
        } else {
          reject(xhr.status);
        }
      }
    };
    // ...
  });
}

4. axios 库封装

在真实的项目中会经常使用到 axios 这样的 ajax 请求的库,虽然可以直接使用,但是往往业务中会有很多接口请求的地方,而这么多的请求有些固定不变的,每个接口在请求时都需要,如:token,baseURL,timeout 等等,针对这样的场景,我们可以对 axios 库进行二次业务封装。对于接口不同的返回结果我们希望有一个全局的提示框,这里我们使用 element-ui 组件库搭配使用。封装后的代码如下:

import axios from 'axios';
import { baseURL } from '@/config'

class Http {
    constructor(baseUrl) {
        this.baseURL = baseURL;
        this.timeout = 3000;
    }
    setInterceptor(instance) {
        instance.interceptors.request.use(config => {
            return config;
        });
        instance.interceptors.response.use(res => {
            if (res.status == 200) {
                return Promise.resolve(res.data);
            } else {
                return Promise.reject(res);
            }
        }, err => {
            return Promise.reject(err);
        });
    }
    mergeOptions(options) {
        return {
            baseURL: this.baseURL,
            timeout: this.timeout,
            ...options
        }
    }
    request(options) {
        const instance = axios.create();
        const opts = this.mergeOptions(options);
        this.setInterceptor(instance);
        return instance(opts);
    }
    get(url, config = {}) {
        return this.request({
            method: 'get',
            url: url,
            ...config
        })
    }
    post(url, data) {
        return this.request({
            method: 'post',
            url,
            data
        })
    }
}
export default new Http;

5. 小结

本节我们通过真实的业务场景触发,对原生的 ajax 请求做了 promise 封装,最后我们对真实的业务场景使用 axios 对业务二次封装,这样更好地复用业务逻辑,统一增加不同返回结果的提示。