大白话 Promise 完整实现

首先,Promise 中有三种状态:pending(等待态)、fulfilled(完成态)、rejected(拒绝态),并且这只能从 pending 到 fulfilled 或者从 pending 到 rejected,并且此过程不可逆。

我们先来实现 Promise 的基本结构:

当我们 new 一个 Promise 时,会传入一个执行函数:new Promise((resolve, reject) => {})

这个执行函数里有两个参数,resolve、reject 方法都是在 Promise 类里定义的,外部调用这个方法时,还可以往里面传入参数,Promise 拿到这个值后保存起来,以供 .then 调用

.then 方法需要指定成功的回调和失败的回调,根据 state 状态的不同来执行对应的回调

class Promise{
  constructor(executor){
    this.state = "pending"  // 初始为 pending 态
    this.value = ''
    this.reson = ''
    const resolve = (value) => {
      this.value = value  // 将传入的 value 保存起来,以便 then 调用
      if(this.state === 'pending'){ // 只能从 pending 变到 fulfilled
        this.state = 'fulfilled'  // resolve 后,将状态置为 fulfilled
      }
    }
    const reject = (reson) => {
      this.reson = reson
      if(this.state === 'pending'){
        this.state = 'rejected'
      }
    }

    executor(resolve, reject)
  }
  then(onfulfilled, onrejected){
    if(this.state === 'fulfilled'){
      onfulfilled(this.value) // 将之前 resolve 的值传入
    }else if(this.state === 'rejected'){
      onrejected(this.reson)  // 将之前 reject 的值传入
    }
  }
}

以上,我们实现了两个功能:

  1. 实现了 Promise 基本结构和状态切换
  2. 实现基本的 then 方法,根据状态判断执行的回调

但是,上面的 Promise 还不能支持异步代码。

因为当 then 执行时,只对 fulfilled、rejected 两种状态做了判断,如果代码是异步的话,当执行到 then 方法时,resolve、reject 方法还没执行,还是 pending 态,所以我们这里需要对 pending 态做处理:

暂时将回调保存起来,等到 resolve、reject 执行的时候,才去执行对应的回调

class Promise{
  constructor(executor){
    this.state = "pending"
    this.value = ''
    this.reson = ''
    this.onfulfilledCallbacks = []  // 存放 .then 中成功的回调
    this.onrejectedCallbacks = [] // 存放 .then 中失败的回调
    const resolve = (value) => {
      this.value = value
      if(this.state === 'pending'){
        this.state = 'fulfilled'
      }
      this.onfulfilledCallbacks.forEach(fn => fn(this.value)) // 当 resolve 执行时,执行 then 中指定的成功回调
    }
    const reject = (reson) => {
      this.reson = reson
      if(this.state === 'pending'){
        this.state = 'rejected'
        this.onrejectedCallbacks.forEach(fn => fn(this.reson))
      }
    }

    executor(resolve, reject)
  }
  then(onfulfilled, onrejected){
    if(this.state === 'fulfilled'){
      onfulfilled(this.value)
    }else if(this.state === 'rejected'){
      onrejected(this.reson)
    }else if(this.state === 'pending'){ // 当 state 还未变化时,先将成功和失败的回调存起来
      this.onfulfilledCallbacks.push(onfulfilled)
      this.onrejectedCallbacks.push(onrejected)
    }
  }
}

以上,我们已经实现了:

  1. 实现了 Promise 基本结构和状态切换
  2. 实现基本的 then 方法,根据状态判断执行的回调
  3. 支持 Promise 异步

但是目前我们的 .then 只能调用一次,还不能实现链式调用 .then,要实现也很简单,只需要让 then 方法返回一个 promise 实例即可。

但是注意:这里返回的 promise 实例必须是一个全新的 promise,这样才能保证后续 then 中的状态可以改变,不然的话从 newPromise 之后状态就一直保持成一个了。。。

同时,then 的回调执行的返回值还会传给下一个 then,所以还要把返回值 resolve 出去

then(onfulfilled, onrejected){
  const promise2 = new Promise((resolve, reject) => {
    if(this.state === 'fulfilled'){
      const x = onfulfilled(this.value)
      resolve(x)
    }else if(this.state === 'rejected'){
      const r = onrejected(this.reson)
      resolve(r)
    }else if(this.state === 'pending'){
      this.onfulfilledCallbacks.push((value) => {
        const x = onfulfilled(value)
        resolve(x)  // 将回调执行的返回值 resolve 出去,resolve 的值会挂载在返回的新的 promise 实例上的 value 属性上,也就是调用了这个实例里的 resolve 方法。下一个 then(也就是返回的这个新 promise 实例的 then) 可以拿到这个值
      })
      this.onrejectedCallbacks.push((reson) => {
        const r = onrejected(reson)
        resolve(r)  // 注意:即使是失败的回调,回调执行完成后,下一个状态依然是 fulfilled,除非出错!(后面我们将处理这个问题)
      })
    }
  })

  return promise2 // 返回一个全新的 promise 实例
}

以上,我们已经实现了:

  1. 实现了 Promise 基本结构和状态切换
  2. 实现基本的 then 方法,根据状态判断执行的回调
  3. 支持 Promise 异步
  4. 每个 then 返回一个新 promise 实例,同时将回调执行的返回值 resolve 出去

我们现在来考虑一个问题,上面 then 中将回调执行的返回值 resolve 直接出去了,如果返回值是一个普通的值的话,这样没有问题。

但是,假如是个 promise,那就不能这样直接 resolve

如果返回值是个 promise 实例,我们就必须调用这个 promisethen,让这个 promise 执行,拿到这个 promise resolve 的值

我们需要写一个方法来处理返回值

then(onfulfilled, onrejected){
  const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {  // 这里加定时器,为了让里面的代码异步,保证传入 promise2 的时候, promise2 已经初始化完了,这也就解释了为什么 Promise 的 then 方法是异步的 
      if(this.state === 'fulfilled'){
        const x = onfulfilled(this.value)
        resolvePromise(promise2, x, resolve, reject)
      }
      
      else if(this.state === 'rejected'){
        const r = onrejected(this.reson)
        resolvePromise(promise2, r, resolve, reject)
      }
      
      else if(this.state === 'pending'){
        this.onfulfilledCallbacks.push((value) => {
          const x = onfulfilled(value)
          resolvePromise(promise2, x, resolve, reject) // 用特定的方法处理返回值
        })
        this.onrejectedCallbacks.push((reson) => {
          const r = onrejected(reson)
          resolvePromise(promise2, r, resolve, reject)
        })
      }
    }, 0)
  })

  return promise2
}


function resolvePromise(promise2, x, resolve, reject) {
  // 判断 x,如果是一般值直接 resolve,如果是一个 promise,要等待 promise resolve 或 reject
  if (promise2 === x) {
    return new TypeError('循环引用!')
  }

  else if (x instanceof Promise) {  // x 是 promise 实例
    x.then(y => {
      resolvePromise(promise2, y, resolve, reject)  // 此时的 y 可能还是一个 promise,所以需要递归调用 resolvePromise,直到解析出一个普通值,就将这个值通过 "传进来的 promise2 的 resolve 方法" 传出去
    }, r => {
      reject(r) // x 内部 reject 了或出错了
    })
  }
  
  else { // 普通值,直接 resolve
    resolve(x)
  }
}

这段代码需要注意的地方比较多:

  1. 为了保证 promise2 可以拿到,加了一个定时器使代码异步
  2. 对 then 中所有 resolve 的值都需要采用 resolvePromise 方法,因为回调函数的返回值可能是 promise 实例
  3. resolvePromise 方法中,需要校验是否循环引用,同时需要注意 promise resolve 的值还是 promise 的情况

以上,我们实现的功能有:

  1. 实现了 Promise 基本结构和状态切换
  2. 实现基本的 then 方法,根据状态判断执行的回调
  3. 支持 Promise 异步
  4. 每个 then 返回一个新 promise 实例,对回调执行的返回值进行判断,如果是普通值就直接 resolve 出去,如果是一个 promise 实例,就执行 then 方法,直到得到一个普通值。同时,我们让 then 方法变成异步的了

接下来要考虑的就是,当我们第一次 new Promise 的时候,如果 resolve 的值也是一个 promise,也需要等待这个 promise 执行完 then,如下:

new Promise((resolve, reject) => {
  resolve(Promise.resolve(123))
})
.then(d => {
  console.log(d)  // 123
})

所以我们需要对 resolve 方法进行完善

const resolve = (value) => {
  if(value instanceof Promise){// 第一次 new Promise 时,resolve 的值如果是一个 promise
    // 调用 then,传入 resolve,resolve 中又进行是否为 promise 的判断调用,递归调用直到 value 不是一个 promise
    return value.then(resolve, reject)
  }
  if (this.state === 'pending') { // 只有 pedding 态可修改,并且不可逆
    this.state = 'fulfilled'
    this.value = value
    this.onfulfilledCallbacks.forEach(fn => fn(value)) // 执行 resolve 回调
  }
}

以上,我们实现的功能有:

  1. 实现了 Promise 基本结构和状态切换
  2. 实现基本的 then 方法,根据状态判断执行的回调
  3. 支持 Promise 异步
  4. 每个 then 返回一个新 promise 实例,对回调执行的返回值进行处理。同时,让 then 方法变成异步
  5. 对第一次 new Promise 时 resolve 的值进行 Promise / 非Promise 的处理

回顾一下我们上面的代码,好像一直没有出现 reject 的情况,在 Promise 中,出现 reject 的情况有几种:

  1. 发生错误
  2. 主动 reject

我们对代码中需要进行错误捕获的地方做一下处理,顺便把 catch 方法实现了

class Promise {
  constructor(executor) {
    this.state = "pending"
    this.value = ''
    this.reson = ''
    this.onfulfilledCallbacks = []
    this.onrejectedCallbacks = [] 
    const resolve = (value) => {
      if (value instanceof Promise) {
        return value.then(resolve, reject)
      }
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value 
        this.onfulfilledCallbacks.forEach(fn => fn(value))
      }
    }
    const reject = (reson) => {
      this.reson = reson
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.onrejectedCallbacks.forEach(fn => fn(this.reson))
      }
    }

    try { // 错误捕获
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  then(onfulfilled, onrejected) {
    const promise2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        if (this.state === 'fulfilled') {
          try { // 错误捕获
            const x = onfulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }

        else if (this.state === 'rejected') {
          try { // 错误捕获
            const r = onrejected(this.reson)
            resolvePromise(promise2, r, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }

        else if (this.state === 'pending') {
          this.onfulfilledCallbacks.push((value) => {
            try { // 错误捕获
              const x = onfulfilled(value) 
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }) 
          this.onrejectedCallbacks.push((reson) => {
            try { // 错误捕获
              const r = onrejected(reson) 
              resolvePromise(promise2, r, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        }
      },0)
    })

    return promise2
  }
  catch(rejectFn){  // 错误处理函数
    return this.then(null, rejectFn)  // catch 其实就是第一个参数为 null 的 then 方法
  }
}

function resolvePromise(promise2, x, resolve, reject) {

  if (promise2 === x) {
    return new TypeError('循环引用!')
  }

  else if (x instanceof Promise) {
    x.then(y => {
      resolvePromise(promise2, y, resolve, reject)
    },
    r => {
      reject(r)
    })
  }

  else {
    resolve(x)
  }
}

我们已经基本完善我们的 Promise 了,不过还需要考虑一种极端情况:

假如我中间传了一个空的 then,new Promise 中 resolve 的值仍然可以被传递下去,被下一个 then 拿到并打印。

new Promise((resolve, reject) => {
  resolve(123)
})
.then()
.then(v => {
  console.log(v)
})

这就需要给 then 传一个默认的函数参数,不传的话默认将拿到的值 return 出去

很简单,一行搞定

then(onfulfilled = v=>v, onrejected = r=>r){
  // ...
}

以上,我们的 Promise 就实现得差不多了,回顾一下我们实现的思路:

  1. 实现了 Promise 基本结构和状态切换
  2. 实现基本的 then 方法,根据状态判断执行的回调
  3. 支持 Promise 异步
  4. 每个 then 返回一个新 promise 实例,对回调执行的返回值进行 Promise / 非Promise 的处理。同时,让 then 方法变成异步
  5. 对第一次 new Promise 时 resolve 的值进行 Promise / 非Promise 的处理
  6. 给 Promise 内部添加错误处理和 catch 方法
  7. 给 then 设置默认参数,不传的时候默认把值传递下去

还剩下一些 Promise 类的静态方法,这里我们也一并实现了:

Promise.resolve = function(value){
  //  Promise.resolve 实际上就是创建一个新的 Promise 实例返回,同时将传入的 value 参数 resolve 出去
  return new Promise((resolve, reject) => {
    resolve(value)
  })
}
Promise.reject = function(reson){
  return new Promise((resolve, reject) => { // Promise.reject 原理同上
    reject(reson)
  })
}

这里着重讲一下 Promise.allPromise.race 方法

  1. Promise.all:
    Promise.all 接收一个任务数组,数组元素 (可能是 promise 或其他值) 并发执行,如果是 Promise 就执行他,拿到 resolve 的值,如果是其他普通值就直接存起来,执行完成后的值存放在一个结果数组

    Promise.all 执行后会返回一个 新的 Promise 实例,并且会将结果数组 resolve 出去,下一个 then 中可以拿到

很明显,我们只需要在内部实现一个计数器,每个任务元素完成后将计数器加 1,只要达到了任务数组的 length 长度即可

  1. Promise.race
    这个方法就是:比比谁最快执行完

    遍历数组参数,执行每一个元素 (同样的,注意区分 Promise 和 非Promise)

    对于 Promise 实例,Promise 执行完成后,直接 resolve

    对于普通值,直接 resolve
Promise.all = function (p) {
  return new Promise((resolve, reject) => {
    let count = 0
    const result = []  // 结果数组

    function processData(index, value) {  // 存放每个 任务执行完后的值,并计数,计数完成后将 result 数组 resolve 出去
      result[index] = value
      if (++count === p.length) {
        resolve(result)
      }
    }
    p.forEach((cur, index) => {
      if (cur instanceof Promise) { // promise 实例
          cur.then(v => {
            processData(index, v)
          }, r => {
            reject(r) // 只要任何一个 promise 出错,就 reject
          })
      } else { // 普通值
        processData(index, cur)
      }
    })
  })
}

Promise.race = function (p) {
  return new Promise((resolve, reject) => { // 只要一个完成了,直接 resolve,resolve 出来的值就是最快执行完的
    p.forEach(cur => {
      if(cur instanceof Promise){
        cur.then(r => { // 执行 promise 然后 resolve 
          resolve(r)
        })
      }else { // 不是 promise,直接 resolve
        resolve(cur)
      }
    })
  })
}

以上就完成了对 Promise 的编写,完整代码如下 (无注释版):

class Promise {
  constructor(executor) {
    this.state = "pending"
    this.value = ''
    this.reson = ''
    this.onfulfilledCallbacks = []
    this.onrejectedCallbacks = [] 
    const resolve = (value) => {
      if (value instanceof Promise) {
        return value.then(resolve, reject)
      }
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value 
        this.onfulfilledCallbacks.forEach(fn => fn(value))
      }
    }
    const reject = (reson) => {
      this.reson = reson
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.onrejectedCallbacks.forEach(fn => fn(this.reson))
      }
    }

    try {
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  then(onfulfilled = v=>v, onrejected = r=>r) {
    const promise2 = new Promise((resolve, reject) =>{
      setTimeout(() =>{
        if (this.state === 'fulfilled') {
          try {
            const x = onfulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }

        else if (this.state === 'rejected') {
          try {
            const r = onrejected(this.reson)
            resolvePromise(promise2, r, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }

        else if (this.state === 'pending') {
          this.onfulfilledCallbacks.push((value) =>{
            try {
              const x = onfulfilled(value) 
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }) 
          this.onrejectedCallbacks.push((reson) =>{
            try {
              const r = onrejected(reson) 
              resolvePromise(promise2, r, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        }
      },0)
    })

    return promise2
  }
  catch(rejectFn){
    return this.then(null, rejectFn)
  }

}
function resolvePromise(promise2, x, resolve, reject) {

  if (promise2 === x) {
    return new TypeError('循环引用!')
  }

  else if (x instanceof Promise) {
    x.then(y => {
      resolvePromise(promise2, y, resolve, reject)
    },
    r => {
      reject(r)
    })
  }

  else {
    resolve(x)
  }
}
Promise.resolve = function(value){
  return new Promise((resolve, reject) => {
    resolve(value)
  })
}
Promise.reject = function(reson){
  return new Promise((resolve, reject) => {
    reject(reson)
  })
}
Promise.all = function (p) {
  return new Promise((resolve, reject) => {
    let count = 0
    const result = []

    function processData(index, value) {
      result[index] = value
      if (++count === p.length) {
        resolve(result)
      }
    }
    p.forEach((cur, index) => {
      if (cur instanceof Promise) {
          cur.then(v => {
            processData(index, v)
          }, r => {
            reject(r)
          })
      } else {
        processData(index, cur)
      }
    })
  })
}
Promise.race = function (p) {
  return new Promise((resolve, reject) => {
    p.forEach(cur => {
      if(cur instanceof Promise){
        cur.then(r => {
          resolve(r)
        })
      }else { 
        resolve(cur)
      }
    })
  })
}

感谢你看到这里 ()

欢迎关注更多个人博客

本文正在参与“写编程博客瓜分千元现金”活动,关注公众号“饥人谷”回复“编程博客”参与活动。

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

推荐阅读更多精彩内容