多接口请求-单loading的实现方案

2,145 阅读2分钟

场景

在前端中,尤其是移动端,常要求接口调用时唤起loading组件,然后在接口返回数据后隐藏。

通常为了繁琐的显示调用loading的show方法,我们会在全局的接口配置中设置。

同时,主流的组件库会通过设置单例模式来避免一个页面出现多个loading组件的情况。

但单例模式并不能解决这个场景:一个页面有多个需要loading组件接口请求。

如果仅仅只是设置了单例模式,那么loading组件就会在第一个接口返回数据后被隐藏。

这当然是可以利用Promise.all来解决,但又失去全局配置的效果,每次多接口调用都需要组织一个Promise.all,然后显示的调用和隐藏loading。

然而这个问题是可以通过扩展传统单例模式来解决的,

解决方案

即除了为loading配置单例模式外,增加一个计数器,当配置了需要loading的接口调用时,使计数器加一。

每次接口数据的返回,先使计数器减一,再判断计数器是否小于等于零,如果是,则清除loading

程序流程图

发送接口请求时

接口返回数据时

源码实现

每此接口调用都要求一个LoadConfig类的实例,如果没有手动传入,则初始化一个默认的LoadConfig类的实例。

LoadingConfig类

import { Toast } from 'vant';

export class LoadConfig {
  static loading = null;
  clearLoading() {
    LoadConfig.loading = null;
  }
  constructor({
    needLoading = true,
    needError = true,
    needSuccess = false,
    loadingMsg = '加载中...',
    errorMsg = '',
    successMsg = '',
  } = {}) {
    this.needLoading = needLoading; // 是否需要loading
    this.needError = needError; // 是否显示错误提示
    this.needSuccess = needSuccess; // 是否显示成功提示
    this.loadingMsg = loadingMsg; // loading的文案
    this.errorMsg = errorMsg; // 错误提示文案
    this.successMsg = successMsg; // 成功提示文案
    this.$loading = null; // loading实例
  }
  // 初始化loading并做不需要loading情况的拦截
  loading() {
    if (!this.needLoading) {
      return;
    }
    // 实例不存在则初始化实例
    if (!LoadConfig.loading) {
      this.$loading = LoadConfig.loading = Toast.loading({
        duration: 0,
        message: this.loadingMsg,
      });
      const clear = LoadConfig.loading.clear;
      // 扩展clear方法
      this.$loading.clear = LoadConfig.loading.clear = (...args) => {
        LoadConfig.clearLoading();
        return clear.apply(LoadConfig.loading, args);
      };
    }
    // loading是单例,所以要记录调用了几次 等所以接口都返回数据后再清除loading
    LoadConfig.loading._count ? (LoadConfig.loading._count += 1) : (LoadConfig.loading._count = 1);
  }
  // 失败时调用的钩子
  fail(msg) {
    this._beforeResult();
    if (!this.needError) {
      return;
    }
    // 如果需要显示错误信息,则显示错误信息并强制隐藏loading
    Toast.fail({
      duration: 1000,
      message: this.errorMsg || msg,
    });
  }

  success(msg) {
    this._beforeResult();
    if (!this.needSuccess) {
      return;
    }
    // 如果需要显示成功信息,则显示成功信息并强制隐藏loading
    Toast.success({
      duration: 1000,
      message: this.successMsg || msg,
    });
  }
  // 当计数小于对于零时,清除loading
  clearLoading() {
    if (!LoadConfig.loading) {
      return;
    }
    LoadConfig.loading._count--;
    if (LoadConfig.loading && LoadConfig.loading._count <= 0) {
      LoadConfig.loading.clear();
    }
  }
  // 每次接口返回数据(无论成功失败)都会调用的钩子
  _beforeResult() {
    this.needLoading && this.clearLoading();
  }
}


接口配置

import axios from 'axios'
const Api = {}

Api.get = (url, config = new LoadConfig()) => {
  config.loading();
  return new Promise((resolve, reject) => {
    axios
      .get(url)
      .then(response => {
        config.success();
        resolve(response);
      })
      .catch(error => {
        config.fail(error);
        reject(error);
      });
  });
};

Api.post = (url, data, config = new LoadConfig()) => {
  config.loading();
  return new Promise((resolve, reject) => {
    axios
      .post(url, data)
      .then(response => {
        config.success();
        resolve(response);
      })
      .catch(error => {
        config.fail(error);
        reject(error);
      });
  });
};

export default Api