my-promise/src/Promise.vue

<template>
  <component :is="tag" class="my-promise" :class="classes">
    <slot v-if="$scopedSlots.combine" name="combine" v-bind="combine"></slot>
    <slot v-if="$scopedSlots.pending && !resolved && isDelayElapsed" name="pending" v-bind="data"></slot>
    <slot v-if="resolved && !error" v-bind="data"></slot>
    <slot v-if="$scopedSlots.error && error" name="error" :error="error"></slot>
  </component>
</template>

<script>
  /**
   * 异步数据组件
   * @module $ui/components/my-promise
   */

  /**
   * 作用域插槽
   * @member scopedSlots
   * @property {string} [default] 默认插槽,请求成功响应数据显示的内容,回调参数 data:resolve的数据
   * @property {string} [pending] 定义请求等待中显示内容,通常做loading效果,回调参数 data:上次resolve的数据
   * @property {string} [error] 定义请求失败后的显示内容,回调参数 error: reject数据
   * @property {string} [combine] 全部状态合并成一个插槽定义,回调参数 pending,data, error,delayOver
   */
  export default {
    name: 'MyPromise',
    /**
     * 属性参数
     * @member props
     * @property {string} [tag=span] 容器标签名
     * @property {Promise} [promise] Promise实例
     * @property {Number} [delay=200] 延时显示loading,即pending时间大于这个时间才会显示
     */
    props: {
      // 容器标签
      tag: {
        type: String,
        default: 'span'
      },
      // Promise
      promise: {
        type: [Object, Promise],
        validator(p) {
          return p && typeof p.then === 'function' && typeof p.catch === 'function'
        }
      },
      // 延时显示loading
      delay: {
        type: Number,
        default: 200
      }
    },
    data() {
      this.timerId = null
      return {
        resolved: false,
        data: null,
        error: null,
        isDelayElapsed: false
      }
    },
    computed: {
      classes() {
        return {
          'is-pending': !this.resolved,
          'is-error': !!this.error,
          'is-done': this.resolved
        }
      },
      combine() {
        return {
          pending: !this.resolved,
          data: this.data,
          error: this.error,
          delayOver: this.isDelayElapsed
        }
      }
    },
    watch: {
      promise: {
        immediate: true,
        handler(promise) {
          this.handlePromise(promise)
        }
      }
    },
    methods: {
      setupDelay() {
        if (this.delay > 0) {
          this.isDelayElapsed = false
          this.timerId && clearTimeout(this.timerId)
          this.timerId = setTimeout(() => {
            this.isDelayElapsed = true
          }, this.delay)
        } else {
          this.isDelayElapsed = true
        }
      },
      handlePromise(promise) {
        this.resolved = false;
        this.error = null
        if (!promise) {
          this.data = null
          this.isDelayElapsed = false
          this.timerId && clearTimeout(this.timerId)
          this.timerId = null
          return
        }
        this.setupDelay()
        promise.then(data => {
          // 确保是一致的,有可能在未resolved时,promise已经发生了变化
          if (this.promise === promise) {
            this.data = data
            this.resolved = true
          }
        }).catch(err => {
          if (this.promise === promise) {
            this.error = err
            this.resolved = true
          }
        })
      }
    },
    beforeDestroy() {
      this.timerId && clearTimeout(this.timerId)
    }
  }
</script>