实现Promise很难? 直接手撕!基础版

187 阅读4分钟

Promise

为什么要学习实现promise

因为面试!!! 哈哈哈 也不全是啦~ 学习原理可以在出问题时提供多些定位、解决问题的思路。 并且promise的实现思路。相信我,理解如何实现后,你会觉得:woc! 这种实现思路也太妙了吧!

本文适合了解promise基本使用,但不知道如何实现的小白哦~

promise形成的原因

防止回调地狱的产生。具体详情不在该文章赘述

promise的规范

很重要 设计Promise的思路就是从规范里产生

  1. promise的有三种状态值: pending、fulfilled、rejected
  2. promise拥有立即执行器(函数)exector() 参数为 resolve、rejected
  3. promise默认状态为pending 更改后则不能改变。 只能从pending到resolve 或者pending到reject
  4. promise拥有value保存成功状态值
  5. Promise拥有reason保存失败状态值
  6. promise一定拥有then,then的两个接收参数 then( onFulfilled(), onRejected()) 第一个是成功后执行的函数 第二个是失败后执行的函数

基础版Promise的实现

以下根据思路一步步实现基础版promise

最下面有完整的基础promoise实现

根据2 (下文阿拉伯数字自动对应规范的第几条)首先写个类, 获取传入的立即执行器。(用trycatch捕获 防止出错)

class PromiseT {
  constructor(exector){
        try {
            exector(resolve, reject)
        } catch (error) {
            throw (error)
        }
  }
}

根据1、4、5 定义三种状态并且有为我们的类添加私有属性status来记录状态,添加value为成功状态值、reason为失败状态值。

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class PromiseT {
    constructor(exector) {
        // 记录状态 默认为pending
        this.status = PENDING
        
        // 记录值
        this.value = undefined
        this.reason = undefined
    }
}

根据1-5 需要在resolve跟reject的时候分别改变对应的状态 以及给value或者reason赋值

class PromiseT {
    constructor(exector) {
        let resolve = value => {
            if (this.status === 'PENDING') {
                this.status = FULFILLED
                this.value = value
            }
        }

        let reject = reason => {
            if (this.status === 'PENDING') {
                this.status = REJECTED
                this.reason = reason
            }
        }
    }
}

根据6 拥有then方法。包含两个参数,在resolve时将value值给成功的回调函数,在reject时将reason值给失败的回调函数。

class PromiseT {
    constructor(exector) {
    }

    then(onFulfilled = () => { }, onRejected = () => { }) {
        if (this.status === 'FULFILLED') {
            onFulfilled(this.value)
        }
        if (this.status === 'REJECTED') {
            onRejected(this.reason)
        }
    }
}

至此同步的基础版promise就完成了 下面是完整的基础版promise实现。

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class PromiseT {
    constructor(exector) {
        // 记录状态 默认为pending 1
        this.status = PENDING

        // 记录值 4 5
        this.value = undefined
        this.reason = undefined

        let resolve = value => {
            if (this.status === 'PENDING') {
                this.status = FULFILLED
                this.value = value
            }
        }

        let reject = reason => {
            if (this.status === 'PENDING') {
                this.status = REJECTED
                this.reason = reason
            }
        }

        try {
            exector(resolve, reject)
        } catch (error) {
            throw (error)
        }
    }

    then(onFulfilled = () => { }, onRejected = () => { }) {
        if (this.status === 'FULFILLED') {
            onFulfilled(this.value)
        }
        if (this.status === 'REJECTED') {
            onRejected(this.reason)
        }
    }
    
// 测试代码
new PromiseT((res, rej) => {
    rej(111)
    // res(111)
}).then(res => {
    console.log(`res`, res);
}, err => {
    console.log(`err:`, err);
})
}

异步promise实现

但是 该Promise只能适用于同步代码。异步代码将无法走入成功或失败回调。

例子

new PromiseT((resolve, reject) => {
    console.log('promise立即执行')
    setTimeout(() => {
        resolve('异步代码')
    }, 1000)
  }).then(res => {
    console.log(res)
  }
  // 该例子只会打印‘promise立即执行’ 而不会打印‘异步代码’

原因是因为走到then的时候 由于异步代码resolve还未执行 promise还是处于pending状态。所以then方法里的所有代码都不执行。

那么首先想到要先把需要执行的回调函数储存,等异步代码执行 promise状态更改之后再来把值传给回调函数并且调用。那么什么时候调用呢,在resolve或者reject的时候调用(因为这时候已经获取到需要放在回调函数里的值了)便有了以下三个步骤。

1. 添加私有属性onResolvedCB、onRejectedCB数组存放回调函数

class PromiseT {
    constructor(exector) {
        this.onResolvedCB = []
        this.onRejectedCB = []
    }
}

2. 改造then 状态为pending的时候储存回调函数,且完美兼容同步代码(不会影响到同步代码)

class PromiseT {
    constructor(exector) {
        // 异步处理 成功、失败存放数组
        this.onResolvedCB = []
        this.onRejectedCB = []
    }

    then(onFulfilled = () => { }, onRejected = () => { }) {
        if (this.status === 'FULFILLED') {
            onFulfilled(this.value)
        }
        if (this.status === 'REJECTED') {
            onRejected(this.reason)
        }
        if (this.status === 'PENDING') {
            this.onResolvedCB.push(() => onFulfilled(this.value))
            this.onRejectedCB.push(() => onRejected(this.reason))
        }
    }
}

3. 在resolve或者reject的时候遍历并且onResolvedCB或者onRejectedCB,因为同步时不会有回调函数存储,所以同步代码跟异步代码都能正常运行。(理解后发现这种处理异步并且不影响到同步代码的方法实在是太妙了!)

class PromiseT {
    constructor(exector) {
        // 异步处理 成功、失败存放数组
        this.onResolvedCB = []
        this.onRejectedCB = []

        let resolve = value => {
            if (this.status === 'PENDING') {
                this.status = FULFILLED
                this.value = value

                //异步
                this.onResolvedCB.forEach(fn => fn())
            }
        }

        let reject = reason => {
            if (this.status === 'PENDING') {
                this.status = REJECTED
                this.reason = reason

                //异步
                this.onRejectedCB.forEach(fn => fn())
            }
        }
    }

    then(onFulfilled = () => { }, onRejected = () => { }) {
        if (this.status === 'FULFILLED') {
            onFulfilled(this.value)
        }
        if (this.status === 'REJECTED') {
            onRejected(this.reason)
        }
        if (this.status === 'PENDING') {
            this.onResolvedCB.push(() => onFulfilled(this.value))
            this.onRejectedCB.push(() => onRejected(this.reason))
        }
    }
}

最后异步Promise基础版代码实现

const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class PromiseT {
    constructor(exector) {
        // 记录状态 默认为pending
        this.status = PENDING

        // 记录值
        this.value = undefined
        this.reason = undefined

        // 异步处理 成功、失败存放数组
        this.onResolvedCB = []
        this.onRejectedCB = []

        let resolve = value => {
            if (this.status === 'PENDING') {
                this.status = FULFILLED
                this.value = value

                //异步
                this.onResolvedCB.forEach(fn => fn())
            }
        }

        let reject = reason => {
            if (this.status === 'PENDING') {
                this.status = REJECTED
                this.reason = reason

                //异步
                this.onRejectedCB.forEach(fn => fn())
            }
        }

        try {
            exector(resolve, reject)
        } catch (error) {
            throw (error)
        }
    }

    then(onFulfilled = () => { }, onRejected = () => { }) {
        if (this.status === 'FULFILLED') {
            onFulfilled(this.value)
        }
        if (this.status === 'REJECTED') {
            onRejected(this.reason)
        }
        if (this.status === 'PENDING') {
            this.onResolvedCB.push(() => onFulfilled(this.value))
            this.onRejectedCB.push(() => onRejected(this.reason))
        }
    }
}

new PromiseT((resolve, reject) => {
    console.log('promise进入')
    setTimeout(() => {
      if (true) { 
        resolve('异步代码成功')
      } else {
        reject('异步代码拒绝')
      }
    }, 1000)
  }).then(res => {
    console.log(`1s 后:${res}`)
  }, error => {
    console.log(`1s 后:${error}`)
  })
  

思考问题:1.为什么使用数组的形式来储存回调函数 2.在异步then方法中,回调函数的参数为value/reason,但是当时由于异步,两个值并没有获取到,为什么最后执行的时候却拿到了回调函数对应的参数了呢?

也许两个问题互为对方的答案~

都看到这啦 顺手点个赞啦~ 有什么建议以及想说的欢迎评论区见!