es6:class实现一个promise

前言:promise的js实现网上有很多,但基本都是基于es5的,es6有着更简介的语法,为什么不尝试一下呢?

阶段一:

只支持链式调用不支持其他api

const pending = 'pending'
const resolved = 'resolved'
const rejected = 'rejected'
class MyPromise {
  constructor (cb) {
    this._status = pending // 初始化状态
    this._data = null // 成功传递的消息
    this._error = null // 失败传递的消息
    this.successSubs = [] // resolve后需要执行的回调队列
    this.failSubs = [] // reject后需要执行的回调队列
    if (typeof cb === 'function') {
      cb(this.resolve.bind(this), this.reject.bind(this)) // 绑定当前实例
    } else if(cb && typeof cb !== 'function') {
      return new TypeError('MyPromise constructor must be a function')
    }
  }
  resolve (_data) {
    // 这里应该有两种情况
    // 1 调用then方法后订阅了
    // 2 直接调用Promise.resolve()方法
    // 情况1:
    if (this._status === pending) {
      this._status = resolved
      this._data = _data
      this.successSubs.forEach(fn => fn())
    }
  }
  reject (_error) {
    if (this._status === pending) {
      this._status = rejected
      this._error = _error
      this.failSubs.forEach(fn => fn())
    }
  }
  resolvePromise (x,resolve, reject) {
    // 如果返回的是MyPromise实例
    if (x instanceof MyPromise) {
      x.then(data => {
        resolve(data)
        },
        error => {
          reject(error)
        })
    } else {
      // 普通值:直接执行then返回的新promise方法的resolve,后一个then属于这个实例的,订阅队列也是属于这个实例的
      resolve(x)
    }
  }
  then (success, fail) {
    // 链式调用 后一个then调用前一个then返回的promise实例
    let p = new MyPromise((resolve, reject) => {
      if (this._status === pending) {
        // promise实例还在pending状态调用了then方法,增加订阅者
        if (success && typeof success === 'function') {
          this.successSubs.push(() => {
            try {
              // 用户调用then方法,callback函数,两种情况
              // 1 非promise对象: return 什么就作为参数传递给下个then什么
              // 2 promise对象:择要等用户的promise的有结果才执行下一个函数
              let x = success(this._data)
              // 统一封装到一个函数中处理
              this.resolvePromise(x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        }
        if (fail && typeof fail === 'function') {
          this.failSubs.push(() => {
            try {
              let x  = fail(this._error)
              this.resolvePromise(x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        }
      } else if (this._status === resolved) {
        try {
          let x  = success(this._data)
          this.resolvePromise(x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      } else if(this._status === rejected) {
        try {
          let x  = fail(this._error)
          this.resolvePromise(x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }
    })
    return p
  }
}

阶段二:

实现其他api:

Promise.resolve()和Promise.reject()

这两个方法都返回一个非pending状态的promise对象,同时Promise类才有的方法,实例没有该方法。和es6中class static方法完美契合。

...
 // 直接调用了Promise.resolve方法
  static resolve (value) {
    return new MyPromise(resolve => {
      resolve(value)
    })
  }
  // 直接调用了Promise.reject
  static reject(reason) {
    return new MyPromise((undefined, reject) => {
      reject(reason)
    })
  }
...

Promise.all

Promise.all(iterable): 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

整理思路:
1 判断一个值是否可迭代,可以用Symbol.iterator属性判断,直接用for...of迭代
2 如果全部正常(没有reject)则可以将该实例resolve。
3 调用resolve的时机,因为是异步的,不晓得所有的iterable啥时候执行完,所以每次push的时候判断一下是否可以结束,可以结束就resolve。
4 reject则只需要rejecte当前失败的reason,前面的全部丢弃
5 static私有方法符合需求

static all (iterable) {
    if (iterable[Symbol.iterator]) {
      return new MyPromise((resolve, reject) => {
        let resolveArr = []
        let len = iterable.length
        // 检查是不是所有的promise都完成了
        function checkAll () {
          if (resolveArr.length === len) {
            resolve(resolveArr)
          }
        }
        try {
          for (let x of iterable) {
            // 每项可以是Promise值和其他值
            if (x instanceof MyPromise) {
              x.then(data => {
                resolveArr.push(data)
                checkAll()
              }, reason => {
                reject(reason)
              })
            } else {
              resolveArr.push(x)
              checkAll()
            }
          }
        } catch (e) {
          reject(e)
        }
      })
    } else {
      // 不是可迭代对象:抛出一个错误
      let str = ({}).toString.call(iterable)
      let reg = /^\[object\s([A-Z][a-z]{2,8})\]$/
      let matchArr = str.match(reg)
      let msg = (matchArr && matchArr[1]) || str
      throw new TypeError( msg + ': is not iterable')
    }
  }

测试mdn的例子:
结果返回:

MyPromise

与mdn的一致
原生Promise

再看一个例子:
image.png

原生的promise返回结果顺序和传入的一致,我们实现的是谁先resolve就谁先push,显然还需要保证顺序。
脑海里冒出如下方案:
1 for循环或者forEach提供了参数index, 和值value方便我们直接赋值,但是Set和Map不支持,而且我们结束的判断resolveArr.length === iterable.length有可能会碰到坑。
2 提供一个key,用于标记顺序
目前就想到两个方案,只能选择方案2:

static all (iterable) {
    if (iterable[Symbol.iterator]) {
      return new MyPromise((resolve, reject) => {
        let resolveArr = []
        let len = iterable.length
        // 检查是不是所有的promise都完成了
        function checkAll () {
          if (resolveArr.length === len) {
            resolve(resolveArr.sort((a, b) => {
              return a.key - b.key
            }).map(v => v.data))
          }
        }
        try {
          let key = -1
          for (let x of iterable) {
            key++
            // 每项可以是Promise值和其他值
            if (x instanceof MyPromise) {
              x.then(data => {
                resolveArr.push({data, key})
                checkAll()
              }, reason => {
                reject(reason)
              })
            } else {
              resolveArr.push({data:x, key})
              checkAll()
            }
          }
        } catch (e) {
          reject(e)
        }
      })
    } else {
      // 不是可迭代对象:抛出一个错误
      let str = ({}).toString.call(iterable)
      let reg = /^\[object\s([A-Z][a-z]{2,8})\]$/
      let matchArr = str.match(reg)
      let msg = (matchArr && matchArr[1]) || str
      throw new TypeError( msg + ': is not iterable')
    }
  }

检测一下:


image.png

Promise.race

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

race方法与all方法类似,但是相对来说更简单:

static race (iterable) {
    if (iterable[Symbol.iterator]) {
      return new MyPromise((resolve, reject) => {
        try {
          for (let x of iterable) {
            // 每项可以是Promise值和其他值
            if (x instanceof MyPromise) {
              x.then(data => {
                resolve(data)
              }, reason => {
                reject(reason)
              })
            } else {
              resolve(x)
            }
          }
        } catch (e) {
          reject(e)
        }
      })
    } else {
      // 不是可迭代对象:抛出一个错误
      let str = ({}).toString.call(iterable)
      let reg = /^\[object\s([A-Z][a-z]{2,8})\]$/
      let matchArr = str.match(reg)
      let msg = (matchArr && matchArr[1]) || str
      throw new TypeError( msg + ': is not iterable')
    }
  }

测试mdn例子


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355