手写promise

手写promise

带大家手写一个 promis。在手写之前我会先简单介绍一下为什么要使用promise、promise的基本用法和使用场景。因为我们得了解清楚需求才能更好地实现它。

本文采取一贯保姆式风格,喂到诸君嘴边了。来,大郎,吃药了~

promise 简介

Promise 的出现解决了我们之前处理异步编程使用回调函数,层层嵌套的地狱回调问题,地狱回调易读性差逻辑不清晰,相信谁接手了一个这样的代码都要骂一句上写得什么玩意儿。当然了处理异步编程还有 generator 和 async 和 await等方式,async 和 await 相比于promise又有一些优点。我在最后会简单的介绍一下,但是不会做过多展开。

地狱回调

我们看下这段代码,是不是看过去就不舒服,然而实际写业务代码时只会比这个更复杂,比如发送一个ajax请求1,请求1里有大量的业务处理代码,然后再发送请求2,并且请求2依赖于请求1结果······

let i = 0
setTimeout(() => {
  let i = 1
  console.log(i)
  setTimeout((i) => {
    let j = i + 2
    console.log(j)
    setTimeout(j => {
      let k = j + 3
      console.log(k)
    }, 1000, j);
  }, 1000, i);
}, 1000);

promise 解决了这个问题

上面这段代码用 promise 实现,这样看好像感觉代码量更多了,但是明显代码逻辑更加清晰了,而且嵌套越多,promise 的优势越明显。

let i = 0
let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    i = i + 1
    console.log(i)
    resolve(1)
  }, 1000)
}).then(data => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      i = i + 2
      console.log(i)
      resolve(i)
    }, 1000);
  })
}).then(data => {
  setTimeout(() => {
    i = i + 3
    console.log(i)
  }, 1000);
})

promise 的基本特性及用法

基本特性:(记住这几个特性,这就是我们实现手写 promise 的具体需求,现在不理解也正常,在后续实现过程中就会慢慢理解了)

  • promise 有三种状态,pending等待、fulfilled 完成、rejected 失败
  • pending 可以转为 fulfilled,也可转为 rejected,一旦状态转变了,状态不可再更改
  • resolve 为成功,状态由 pending 转为 fulfilled,并且接收参数 value
  • reject 为失败,状态由 pending 转为 fulfilled,并且接收参数 reason
  • Then 方法接收两个参数分别为 onFulfilled 和 onRejected,当 pending => fulfilled 后执行 onFulFilled,当 pending => rejected 后执行 onRejected
// index.js
new Promise((resolve, reject) => {
  setTimeout(() => {
    // 异步操作成功时
    resolve('hello')
    // 异步操作失败时
    // reject('失败')
  }, 2000)
}).then(data => {
  console.log(data)
}, reason => {
  console.log(reason)
})

Promise 实现

第一步 实现一个最基本的 promise

// index.js ----- 栗子1
let Promise = require('./promise')
let p = new Promise((resolve, reject) => {
  resolve('成功啦~')
  // reject('失败啦~')
})
p.then(data => {
  console.log(data)
})

由上面这段代码我们和promise的特性,我们理一下需求:

  1. promise 要偿还一个和回调方法 executor,作为Promise 的参数
  2. Executor接收resolve 和 reject 作为 executor 的参数,resolve('成功啦'),接收value参数,reject('失败啦~') 接收 reason 参数。
  3. 结合promise 的规定,promise 有三种状态,pending、fulfilled、rejected
  4. 实现一个 then 方法,接收两个参数分别为 onFulfilled 和 onRejected,当 pending => fulfilled 后执行 onFulFilled,当 pending => rejected 后执行 onRejected

代码实现

  1. promise 要偿还一个和回调方法 executor,作为Promise 的参数

    // promise.js
    class Promise {
     constructor(executor) {
        excutor()
      }
    }
    module.exports = Promise
    
  1. Executor接收resolve 和 reject 作为 executor 的参数,resolve('成功啦'),接收value参数,reject('失败啦~') 接收 reason 参数。

    // promise.js
    class Promise {
     constructor(executor) {
        this.value = undefined
        this.reason = undefined
        const resolve = (value) => {
          this.value = value
        }
        const reject = (reason) => {
          this.reason = reason
        }
        excutor(resolve, reject)
      }
    }
    module.exports = Promise
    
  1. 结合promise 的规定,promise 有三种状态,pending、fulfilled、rejected

    // promise.js
    class Promise {
     constructor(executor) {
        // 定义一个状态,初始是 pending
        this.state = 'pending'
        this.value = undefined
        this.reason = undefined
        const resolve = (value) => {
          if (this.state === 'pending') {
          // 成功,状态由 pending 变为 fulfilled
            this.state = 'fulfilled'
            // 接收 成功啦~
            this.value = value
          }
        }
        const reject = (reason) => {
          if(this.state === 'pendinng') {
            // 失败,状态由 pending 变为 rejected
            this.state = 'rejected'
            // 接收 失败啦~
            this.reason = reason
          }
    
        }
        excutor(resolve, reject)
      }
    }
    module.exports = Promise
    
  1. 实现一个 then 方法,接收两个参数分别为 onFulfilled 和 onRejected,当 pending => fulfilled 后执行 onFulFilled,当 pending => rejected 后执行 onRejected

    class Promise {
      constructor(executor) {
        this.state = 'pending'
        this.value = undefined
        this.reason = undefined
        
        const resolve = (value) => {
          if (this.state === 'pending') {
            this.value = value
            this.state = 'fulfilled'
          }
        }
        const reject = (reason) => {
          if (this.state === 'pending') {
            this.reason = reason
            this.state = 'rejected'
          }
        }
        executor(resolve, reject)
      }
      // 实现 then 方法
      then(onFulfilled, onRejected) {
        // 成功
        if (this.state === 'fulfilled') {
         // 接收成功传来的数据
          onFulfilled(this.value)
        }
        // 失败
        if (this.state === 'rejected') {
          // 接收失败传来的数据
          onRejected(this.reason)
        }
      }
    }
    module.exports = Promise
    

    运行一下栗子1成功打印出

    promise01

第二步 实现异步功能 发布订阅模式

很显然 栗子1 是一个同步操作,我们来看下 栗子2,发现在 then 里的 console.log 啥也没打印出来。

// index.js ------ 栗子2
let Promise = require('./promise')
let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('延迟1s 成功啦~')
  }, 1000)
})
p.then(data => {
  console.log(data)
})
  • 这是为什呢?

因为到目前为止,我们实现的 promise 都是同步的,当我们执行 executor 时先把同步操作执行完成,发现有一个异步操作 settimeout,先让她去排队了(这里需要了解一下事件循环机制),然后立刻去同步执行了 then 方法。

也就是说 settimeout 里的 resolve 方法根本没执行,状态也就还是 pending,value 也没有获取到我们传入的 延迟一秒 成功啦~,所以即使执行了 then 方法也没用,因为状态还是 pending。

  • 怎么解决呢?

既然 then 自己无法知道 resolve 什么时候执行,是否执行了,那resolve执行完后就需要有个东西告诉then,执行完了。

铛铛~ 发布订阅者模式,订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

举个栗子,比如我们去吃海底捞,海底捞前台有一个做手膜的福利,我也不知道前台小姐姐还有多少个手膜要做,什么时候轮到我,我就去他公众号上预约,排了一个号,就继续吃我的海底捞了,然后前台小姐姐做完了就通过我在公众号上留的信息,排的号打电话给我叫我我去做手膜。

我们理一下需求:

还在 pending 的时候我们要记录下,哪些是要等到resolve完成后执行的,我们把这些放进一个数组中。当 resolve 或是 reject 后再把它们拿出来执行。

class Promise {
  constructor(executor) {
    this.state = 'pending'
    this.value = undefined
    this.reason = undefined
    // 定义数组,存放稍后要完成的任务
    this.onResolvedCallbacks = []
    this.onRejectedCallbacks = []
    const resolve = (value) => {
      if (this.state === 'pending') {
        this.value = value
        this.state = 'fulfilled'
        /* 成功了,在这个例子中,相当于过了 1秒了开始执行resolve了,
        状态改变后,把我们预约好的任务拿出来依次执行 */
        this.onResolvedCallbacks.forEach(fn => fn())
      }
    }
    const reject = (reason) => {
      if (this.state === 'pending') {
        this.reason = reason
        this.state = 'rejected'
        this.onRejectedCallbacks.forEach(fn => fn())
      }
    }
    executor(resolve, reject)
  }
  then(onFulfilled, onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value)
    }
    if (this.state === 'rejected') {
      onRejected(this.reason)
    }
    if(this.state === 'pending') {
      /* 因为异步导致 state 还在 pending 状态 
      所以把 要做的任务先放到预约的数组队列里
      */
      this.onResolvedCallbacks.push(() => {
        onFulfilled(this.value)
      })
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason)
      })
    }
  }
}
module.exports = Promise
promise02

第三步 链式调用

promise 的一个优势就是链式调用,逻辑清晰,我们能经常会这样链式写,栗子3

// index.js ------ 栗子3
let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('延迟1秒 成功啦~')
  }, 1000)
})
p.then(data => {
  let str = data + '我再第一个 then 里哦!'
  console.log(str)
  return str
  /* return new Promise((resolve, reject) => {
    resolve('分割符')
  }
  */
})
}).then(data => {
  let str = data + '我在第二个 then 里哦!'
  console.log(str)
})

需求:

  1. promise本身就包含 then 方法,要实现链式调用我们直接在 then 里 返回 一个 promise 不就行了吗?把then方法里所有任务都放到promise promise((resolve, reject) => {原来then里的任务}) 然后再接下一个 then。

    class Promise {
      constructor(executor) {
        this.state = 'pending'
        this.value = undefined
        this.reason = undefined
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []
        const resolve = (value) => {
          if (this.state === 'pending') {
            this.value = value
            this.state = 'fulfilled'
            this.onResolvedCallbacks.forEach(fn => fn())
          }
        }
        const reject = (reason) => {
          if (this.state === 'pending') {
            this.reason = reason
            this.state = 'rejected'
            this.onRejectedCallbacks.forEach(fn => fn())
          }
        }
        executor(resolve, reject)
      }
      then(onFulfilled, onRejected) {
        // 为了打到链式调用 then , 我们返回一个新的 promise 实例
        const promise2 = new Promise((resolve, reject) => {
          if (this.state === 'fulfilled') {
            /* 这里的 onFulfilled 的结果不就是栗子3中回调函数的结果吗,然后接着立刻执行 下一个then方法*/
            resolve(onFulfilled(this.value))
          }
          if (this.state === 'rejected') {
            reject(onRejected(this.reason))
          }
          if(this.state === 'pending') {
            this.onResolvedCallbacks.push(() => {
              resolve(onFulfilled(this.value))
            })
            this.onRejectedCallbacks.push(() => {
              reject(onRejected(this.reason))
            })
          }
        })
        return promise2
      }
    }
    module.exports = Promise
    
  1. Then 方法里可能 返回的是个普通的值,也可能返回的是一个 promise。如果是 普通值,我们直接传到 value 里就行了,如果是个 promise 我们还要执行下 promise的 resolve 或是 reject。

    // index.js --- 栗子4 then 里可能返回普通值,也可能返回 promise,这里我们需要处理一下
    let Promise = require('./promise')
    let p = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('延迟1秒 成功啦~')
      }, 1000)
    })
    p.then(data => {
      let str = data + '我再第一个 then 里哦'
      console.log(str)
      return new Promise((resolve, reject) => {
        resolve(str)
      })
    }).then(data => {
      let str = data + '我在第二个 then 里哦'
      console.log(str)
    })
    
    // promise.js
    class Promise {
      constructor(executor) {
        this.state = 'pending'
        this.value = undefined
        this.reason = undefined
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []
        const resolve = (value) => {
          if (this.state === 'pending') {
            this.value = value
            this.state = 'fulfilled'
            this.onResolvedCallbacks.forEach(fn => fn())
          }
        }
        const reject = (reason) => {
          if (this.state === 'pending') {
            this.reason = reason
            this.state = 'rejected'
            this.onRejectedCallbacks.forEach(fn => fn())
          }
        }
        executor(resolve, reject)
      }
      then(onFulfilled, onRejected) {
        const promise2 = new Promise((resolve, reject) => {
          if (this.state === 'fulfilled') {
            /* 因为此时 promise2 还没有生成,所以我们利用 settimeout,
            把里面的内容放到下一个事件循环中执行,那时promise2 已经在这个循环里生成了 */
            setTimeout(() => {
              let x = onFulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            }, 0)
          }
          if (this.state === 'rejected') {
            setTimeout(() => {
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            }, 0)
          }
          if(this.state === 'pending') {
            this.onResolvedCallbacks.push(() => {
              setTimeout(() => {
                let x = onFulfilled(this.value)
                resolvePromise(promise2, x, resolve, reject)
              }, 1000)
            })
            this.onRejectedCallbacks.push(() => {
              setTimeout(() => {
                let x = onRejected(this.reason)
                resolvePromise(promise2, x, resolve, reject)
              }, 0)
            })
          }
        })
        return promise2
      }
    }
    function resolvePromise(promise, x, resolve, reject) {
      if(typeof x === 'function' || (typeof x === 'object' && x !== null)) {
        try {
          const then = x.then
          if (typeof then === 'function') {
            // 判断是一个 x 是一个promise,就执行 then 方法里的两个回调,可能是成功,也可能失败
            then.call(x, y => {
              resolve(y)
            }, r => {
              reject(r)
            })
          }
        } catch (err) {
          reject(err)
        }
      } else {
        // 只是一个普通的值
        resolve(x)
      }
    }
    module.exports = Promise
    

    我们执行一下

    promise04
  2. 如果我们返回的promise里还有promise咋办?递归啊!

    // index.js ----- 栗子5
    let Promise = require('./promise')
    let p = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('延迟1秒 成功啦~')
      }, 1000)
    })
    p.then(data => {
      let str = data + '我再第一个 then 里哦'
      console.log(str)
      // 返回值是 promise 包 promise
      return new Promise((resolve, reject) => {
        resolve(str + new Promise((resolve, reject) => {
          resolve(data + 'promise 中的 promise')
          console.log(str + 'promise 中的 promise')
        }))
      })
    }).then(data => {
      let str = data + '我在第二个 then 里哦'
      console.log(str)
    })
    
    // promise.js
    ....
    function resolvePromise(promise, x, resolve, reject) {
      if(typeof x === 'function' || (typeof x === 'object' && x !== null)) {
        try {
          const then = x.then
          if (typeof then === 'function') {
            then.call(x, y => {
              // 递归
              resolvePromise(promise, y, resolve, reject)
            }, r => {
              reject(r)
            })
          }
        } catch (err) {
          reject(err)
        }
      } else {
        resolve(x)
      }
    }
    ....
    
    promise05

我觉得这样下来就差不多了,promise 当然还有 all 等方法,下次有时间再更,今天乏了,不想写了。

参考文档:

promise

手写promise

史上最最最详细的手写Promise教程

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

推荐阅读更多精彩内容

  • Promise的声明 首先,promise肯定是一个类,我们就用class来声明。 由于new Promise((...
    oWSQo阅读 230评论 0 1
  • 每个promise都有一个内部属性[[PromiseState]]被用来表示Promise的三种状态,初始化的状态...
    王小滚阅读 242评论 0 0
  • 1. promise要解决的问题: 脑筋急转弯:把牛关进冰箱里,要分几步? 很显然,这三个操作不能颠倒顺序,否则任...
    月上秦少阅读 1,565评论 0 3
  • 原文详见:Promise实现原理(附源码)参考文章:BAT前端经典面试问题:史上最最最详细的手写Promise教程...
    张小明_to阅读 99评论 0 1
  • 简单粗暴,全部代码如下: class Promise{ constructor(executor){ t...
    倾城一梦1123阅读 1,530评论 0 1