中秋写的Promise,你确定不进来看看嘛(一)

99 阅读16分钟

前言

警告:此为小白文,大佬慎看!大佬慎看!大佬慎看!

时值中秋,被安排在公司值班,不想只是刷刷抖音来荒废时间,打算研究点什么。

在日常工作中,说起promise,大家都用的飞起,但是一看到promise面试题就懵逼,上来就是一大串then问你输出什么。每次都是看完似乎明白了,过了三天再来看又懵逼了,归根结底,还是没搞清楚底层原理。

废话不多说,来吧,就趁着中秋佳节来手写一个promise.

搭个架子

刚写完“搭个架子”四个字人就懵了,要咋写呢。

还是从用法开始一点点抠吧。

最基础的用法

const p1 = new Promise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
})

console.log(p1)

输出

图片

从上面最基础的用法可以得到以下几个信息

  • 有一个new关键字
  • Promise的参数是一个函数,并且执行了

这里很容易就想到了用class和构造函数,上代码

class myPromise {
    constructor(fn) {
        fn()
    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
})

输出

图片

抛开报错,我们实现了上面得到的两点信息。

接下来分析我们传入的函数

(resolve, reject) => {
    ...
    resolve(123)
}
  • 整个参数是个函数,这个上面已经说了
  • 这个函数参数接收两个参数resolve和reject,并且都是函数

接着写

class myPromise {
    constructor(fn) {
        fn(resolve, reject)
    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
})

输出

图片

写到这里,其实我们很清楚的知道会报错,因为根本就没有定义resolve函数和reject函数

下一步,写resolve函数和reject函数

要写在哪里呢!! constructor构造函数里面是实例化的时候初始化执行的代码,很显然这里面不适合定义这两个函数,那写在外面试试

class myPromise {
    constructor(fn) {
        fn(resolve, reject)
    }
    resolve() {

    }
    reject() {
        
    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
})

输出

图片

what the fuck!!!

为啥还是提示没有定义呢。

来分析一下查找过程

在class里面定义的函数,是挂载在实例上的,比如

class A {
    fn1() {
        console.log('fn1')
    }
}
const a = new A()
a.fn1()

到这里,好像有点眉目了,既然实例上可以访问到class定义的方法,那么我们在constructor里面也改成在实例上调用不就好了。

我们通常在constructor函数里面给实例初始化,通常写法如下:

class Dog {
    constructor(name) {
        this.name = name || '旺财'
    }
    eat() {
        console.log('eat')
    }
}
const dog = new Dog('大黄')
console.log(dog.name)
dog.eat()

输出

大黄
eat

这是最常见的例子,很显然,constructor里面的this就是我们创建的实例

到这里就简单了,我们只需要把resolve和reject改成从this上去不就可以了

class myPromise {
    constructor(fn) {
        fn(this.resolve, this.reject)
    }
    resolve() {

    }
    reject() {

    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
})

输出

图片

搞定,万里长征完成了第一步。喝口水休息一下。

喝完了继续。

现在我们resolve和reject函数里面还是空空如也。得填补起来。

补啥呢,又懵逼了,还是老方法,从用法开始分析吧。

我们看看我们的输出的原生的promise有啥区别。

原生promise基础使用

const p1 = new Promise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
})

console.log(p1)

输出

图片

这啥玩意...

一个一个来分析

  • Prototype 从字面上看知道这是实例的原型 Promise
  • PromiseState 从字面上也能看出这是指状态
  • PromiseResult 从字面上看知道这是结果

好了,第一个Prototype就不细表了,相信大家也都知道,p1是Promise new 出来的实例。

第二个,Promise这个单词有承诺的意思,承诺都是许诺未来的事情,所以它必然有一个状态。在Promise中有三种状态。

  • pending 等待
  • fulfilled 已成功
  • rejected 已失败

我们在上面的例子中已经看到了这个很熟悉的单词fulfilled,它就是表示已成功,也就是这个承诺做到了,那么自然而然它也就结束了。

接下来我们就实现这三种状态。有如下约定:

  • pending 可以转化为 fulfilled,也可以转化为rejected
  • 转化行为是不可逆的,一旦变更就无法修改

先来定义三种状态,这里又有一个问题,这三种状态定义在哪里呢,是在class里面还是外面。

貌似都可以,我们先放里面,实现它再说,也刚好试试class的静态属性

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        fn(this.resolve, this.reject)
    }
    resolve() {

    }
    reject() {

    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
})

PromiseState,这个是实例的状态,所以要定义在constructor构造函数里面。

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        fn(this.resolve, this.reject)
    }
    resolve() {

    }
    reject() {

    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
})

定义好了三种状态,继续分析。

我们知道初始状态是pending, 那它什么时候变化呢,承诺啥时候兑现呢(当然也可以拒绝)。这就刚好要用到我们前面定义的两个函数resolve()和reject()了。

  • 在调用resolve()的时候,pending转化为fulfilled
  • 在调用rejected()的时候,pending转化为rejected

来吧,上代码

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        fn(this.resolve, this.reject)
    }
    resolve() {
        this.PromiseState = myPromise.FULFILLED
    }
    reject() {
        this.PromiseState = myPromise.REJECTED
    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
})

输出

图片

又见红了,哎。

不过好在这个报错我们经常见,小问题,无法给undefined设置属性,也就是说咱们这个resolve()里面的this是undefined。

那为啥这个this不是我们的实例mp1呢。其实也好理解,我们看resolve调用的地方,并不是在mp1上调用的,而是在实例化传入的函数里面调用。因此这个this并不是mp1。

那我们想要的就是要它指向创建的myPromise实例,才能获取实例的PromiseState。

咋办呢,只好拿出改变this指向的三板斧。call、apply、bind

这哥仨具体用法就不在这儿赘述了,有兴趣可以自行查阅其他文章。

还是简单提一下吧,他们有几个小区别。

  • call、apply 会直接调用函数执行
  • bind只是修改this执行,并不会立即执行函数

好了,光着一个区别我们就pass了call和apply

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        fn(this.resolve.bind(this), this.reject.bind(this))
    }
    resolve() {
        this.PromiseState = myPromise.FULFILLED
    }
    reject() {
        this.PromiseState = myPromise.REJECTED
    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
})
console.log(mp1)

输出

图片

看到这里,是不是很像我们上面原生promise的输出了。

来对比看看。

先看看原生promise的表现

const p1 = new Promise((resolve, reject) => {
    resolve(123)
    reject(456)
})
console.log(p1)

输出

图片

再看看我们的

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        fn(this.resolve.bind(this), this.reject.bind(this))
    }
    resolve() {
        this.PromiseState = myPromise.FULFILLED
    }
    reject() {
        this.PromiseState = myPromise.REJECTED
    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
    reject(456)
})
console.log(mp1)

输出

图片

是不是发现问题了,还记得前面的约定嘛。状态一经改变就不可再更改。

下面我们来解决这个问题。只在状态为pending的时候往下执行。

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        fn(this.resolve.bind(this), this.reject.bind(this))
    }
    resolve() {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
        }
    }
    reject() {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
        }
    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
    reject(456)
})
console.log(mp1)

输出

图片

搞定。

等等!还记得这张图嘛

图片

我们还有个promiseResult没实现呢。搞它

我们通过原生的应用发现,PromiseResult和PromiseState一样都是挂载在myPromise实例上。

加上去

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        fn(this.resolve.bind(this), this.reject.bind(this))
    }
    resolve() {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
        }
    }
    reject() {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
        }
    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
    reject(456)
})
console.log(mp1)

加上去了,但这玩意儿是干嘛的呢。再回去看看我们原生的例子。

const p1 = new Promise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
})

console.log(p1)

输出

图片

好办了,这个PromiseResult只是我们resolve()或者reject()的值,我们给他们取个名字,成功的给结果(result),失败的给原因(reason)

改!

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        fn(this.resolve.bind(this), this.reject.bind(this))
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
        }
    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log('我执行了!')
    resolve(123)
})
console.log(mp1)

输出

图片

有那味了。 我们试试失败的情况。

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        fn(this.resolve.bind(this), this.reject.bind(this))
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
        }
    }
}
const mp1 = new myPromise((resolve, reject) => {
    resolve(123)
})
const mp2 = new myPromise((resolve, reject) => {
    reject(456)
}) 
console.log(mp1)
console.log(mp2)

输出

图片

大功告成,第一段落结束。

搞then

才三点,喝口水继续干。 接下来开始写then方法,还是老样子,我们从用法开始分析。 用过的朋友都知道,then方法接收两个函数参数。

const p1 = new Promise((resolve, reject) => {
    resolve(123)
})
const p2 = new Promise((resolve, reject) => {
    reject(456)
})
p1.then((result) => {
    console.log(result)
    console.log(p1)
}, (reason) => {
    console.log(reason)
})
p2.then((result) => {
    console.log(result)
}, (reason) => {
    console.log(reason)
    console.log(p2)
})

输出

图片

看到这儿,好像发现了些什么,这个result和reason不就是咱们前面写的PromiseResult嘛。

也就是说,咱们的PromiseResult给了这个then方法第一个函数参数或者第二个函数参数的参数,好像有点拗口,能理解就行。

到这里,咱们得到几点信息:

  • then方法是挂载在咱们实例上的
  • then 方法接收两个函数参数
  • 这两个个函数参数的参数是咱们PromiseResult的值
  • 如果PromiseState的状态变为fulfilled,就执行第一个函数参数
  • 如果PromiseState的状态变为rejected,就执行第二个函数参数

好吧,开搞.

我们将他们命名为onFulfilled和onRejected。

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        fn(this.resolve.bind(this), this.reject.bind(this))
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
        }
    }
    then(onFulfilled, onRejected) {
        if (this.PromiseState === myPromise.FULFILLED) {
            onFulfilled(this.PromiseResult)
        }
        if (this.PromiseState === myPromise.REJECTED) {
            onRejected(this.PromiseResult)
        }
    }
}
const mp1 = new myPromise((resolve, reject) => {
    resolve(123)
})
const mp2 = new myPromise((resolve, reject) => {
    reject(456)
}) 
mp1.then(result => {
    console.log(result)
    console.log(mp1)
}, reason => {
    console.log(reason)
})
mp2.then(result => {
    console.log(result)
}, reason => {
    console.log(reason)
    console.log(mp2)
})

输出

图片

到这里,发现和我们原生的基本上差不多了。

下面我们来测试一下异常的情况,因为promise里面的代码不可控。

我们加一段错误的代码

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        fn(this.resolve.bind(this), this.reject.bind(this))
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
        }
    }
    then(onFulfilled, onRejected) {
        if (this.PromiseState === myPromise.FULFILLED) {
            onFulfilled(this.PromiseResult)
        }
        if (this.PromiseState === myPromise.REJECTED) {
            onRejected(this.PromiseResult)
        }
    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log(abc)
    resolve(123)
})
const mp2 = new myPromise((resolve, reject) => {
    reject(456)
}) 
mp1.then(result => {
    console.log('fulfilled:' + result)
    console.log(mp1)
}, reason => {
    console.log('rejected:' + reason)
})
mp2.then(result => {
    console.log('fulfilled:' + result)
}, reason => {
    console.log('rejected:' + reason)
    console.log(mp2)
})

输出

图片

啊哦,报错了,对比一下原生promise,看会输出什么

const p1 = new Promise((resolve, reject) => {
    console.log(abc)
    resolve(123)
})
const p2 = new Promise((resolve, reject) => {
    reject(456)
})
p1.then((result) => {
    console.log('fulfilled:' + result)
    console.log(p1)
}, (reason) => {
    console.log('rejected:' + reason)
})
p2.then((result) => {
    console.log('fulfilled:' + result)
}, (reason) => {
    console.log('rejected:' + reason)
    console.log(p2)
})

输出

图片

可以看到原生的promise处理,虽然有报错,依然往后执行了reject。

我们可以做如下处理:用try catch来捕获可能出现的错误,然后手动执行reject

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        try {
            fn(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
        }
    }
    then(onFulfilled, onRejected) {
        if (this.PromiseState === myPromise.FULFILLED) {
            onFulfilled(this.PromiseResult)
        }
        if (this.PromiseState === myPromise.REJECTED) {
            onRejected(this.PromiseResult)
        }
    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log(abc)
    resolve(123)
})
const mp2 = new myPromise((resolve, reject) => {
    reject(456)
}) 
mp1.then(result => {
    console.log('fulfilled:' + result)
    console.log(mp1)
}, reason => {
    console.log('rejected:' + reason)
})
mp2.then(result => {
    console.log('fulfilled:' + result)
}, reason => {
    console.log('rejected:' + reason)
    console.log(mp2)
})

输出

图片

得到了我们想要的结果,和原生promise表现一致了。

但是仔细一想,好像又有点不对劲。

我们平时的习惯,好像很少写then的第二个参数,而是在then后面写catch函数。

这又是啥玩意呢。上代码,看看原生的

const p1 = new Promise((resolve, reject) => {
    reject(456)
})
p1.then((result) => {
    console.log('fulfilled:' + result)
    console.log(p1)
}, (reason) => {
    console.log('rejected:' + reason)
})
p1.catch(error => {
    console.log('error:' + error)
})

输出

图片

看到这里,神奇的发现,then方法的第二个函数参数和catch方法的函数参数都执行了!!!

是不是这两个是同一个东西呢。

我们再来试验一下另一种情况。

const p1 = new Promise((resolve, reject) => {
    console.log(abc)
    reject(456)
})
p1.then((result) => {
    console.log('fulfilled:' + result)
    console.log(p1)
}, (reason) => {
    console.log('rejected:' + reason)
})
p1.catch(error => {
    console.log('error:' + error)
})

输出

图片

到这里可以发现,catch的行为和then的第二个函数参数是一致的。那我们可以先简单认为catch是then第二个函数参数的一个语法糖。

来手写一个试试

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        try {
            fn(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
        }
    }
    then(onFulfilled, onRejected) {
        if (this.PromiseState === myPromise.FULFILLED) {
            onFulfilled(this.PromiseResult)
        }
        if (this.PromiseState === myPromise.REJECTED) {
            onRejected(this.PromiseResult)
        }
    }
    catch(onError) {
        this.then(null, onError)
    }
}
const mp1 = new myPromise((resolve, reject) => {
    console.log(abc)
    resolve(123)
})
mp1.then(result => {
    console.log('fulfilled:' + result)
    console.log(mp1)
}, reason => {
    console.log('rejected:' + reason)
})
mp1.catch(error => {
    console.log('error:' + error)
})

输出

图片

是不是到这里就完美了呢,再测试一下不走reject的情况

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        try {
            fn(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
        }
    }
    then(onFulfilled, onRejected) {
        if (this.PromiseState === myPromise.FULFILLED) {
            onFulfilled(this.PromiseResult)
        }
        if (this.PromiseState === myPromise.REJECTED) {
            onRejected(this.PromiseResult)
        }
    }
    catch(onError) {
        this.then(null, onError)
    }
}
const mp1 = new myPromise((resolve, reject) => {
    resolve(123)
})
const mp2 = new myPromise((resolve, reject) => {
    reject(456)
}) 
mp1.then(result => {
    console.log('fulfilled:' + result)
    console.log(mp1)
}, reason => {
    console.log('rejected:' + reason)
})
mp1.catch(error => {
    console.log('error:' + error)
})

输出

图片

到这儿,出现报错了,这个问题也很容易理解,我们在调用catch的时候,相当于调用了then,然后第一个参数传入了null,在执行onFulfilled(this.PromiseResult)的时候出现了问题。

像这类问题,很容易想到加上类型判断。

要处理传入的不为function的情况。我们做以下处理:

  • 如果传入的是function,就原封不动的返回这个function
  • 如果传入的不是function
    • 把第一个参数onFulfilled包装成函数,返回这个值
    • 把第二个参数onRejected同样包装成函数,在函数体中抛出这个错误
class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        try {
            fn(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : (reason) => {
            throw reason
        } 
        if (this.PromiseState === myPromise.FULFILLED) {
            onFulfilled(this.PromiseResult)
        }
        if (this.PromiseState === myPromise.REJECTED) {
            onRejected(this.PromiseResult)
        }
    }
    catch(onError) {
        this.then(null, onError)
    }
}
const mp1 = new myPromise((resolve, reject) => {
    resolve(123)
})
mp1.then(result => {
    console.log('fulfilled:' + result)
    console.log(mp1)
}, reason => {
    console.log('rejected:' + reason)
})
mp1.catch(error => {
    console.log('error:' + error)
})

输出

图片

到这里,我们的实现算是告一段落了。

等等,总感觉哪里不对劲,咱们promise的核心不是用来解决异步问题的嘛,怎么从头到尾都是同步代码。

也就是我们写了那么多,还没有触及核心问题。

莫慌,我们上面写的东西已经把promise的基础框架搭建好了,接下来就是啃异步这个硬骨头了。

还是老样子,从原生promise的用法开始看。

console.log(1)
const p = new Promise((resolve, reject) => {
    console.log(2)
    resolve('成功了!')
})

p.then((result) => {
    console.log(result)
})

console.log(3)

输出

1
2
3
成功了

再来看看我们的

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        try {
            fn(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : (reason) => {
            throw reason
        } 
        if (this.PromiseState === myPromise.FULFILLED) {
            onFulfilled(this.PromiseResult)
        }
        if (this.PromiseState === myPromise.REJECTED) {
            onRejected(this.PromiseResult)
        }
    }
    catch(onError) {
        this.then(null, onError)
    }
}
console.log(1)
const mp1 = new myPromise((resolve, reject) => {
    console.log(2)
    resolve('成功了')
})
mp1.then((result) => {
    console.log(result)
})
console.log(3)

输出

1
2
成功了
3

通过我们自己的promise和原生promise对比,可以看到执行结果的区别在于resolve的值。

我们自己的很好理解,就是按照顺序同步执行下来。

接下来对我们自己的promise做改造,让它能符合原生promise的执行顺序。

resolve的结果需要在当前执行栈所有同步代码执行完毕后执行。这里我们第一个想到的是setTimeout,

既然我们像要resolve()结果的输出在当前栈同步代码之后,先给resolve函数里面的代码用setTimeout包裹一下。

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        try {
            fn(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            setTimeout(() => {
                this.PromiseState = myPromise.FULFILLED
                this.PromiseResult = result
            })
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            setTimeout(() => {
                this.PromiseState = myPromise.REJECTED
                this.PromiseResult = reason
            })
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : (reason) => {
            throw reason
        } 
        if (this.PromiseState === myPromise.FULFILLED) {
            onFulfilled(this.PromiseResult)
        }
        if (this.PromiseState === myPromise.REJECTED) {
            onRejected(this.PromiseResult)
        }
    }
    catch(onError) {
        this.then(null, onError)
    }
}
console.log(1)
const mp1 = new myPromise((resolve, reject) => {
    console.log(2)
    resolve('成功了')
})
mp1.then((result) => {
    console.log(result)
})
console.log(3)

输出

1
2
3

诶,直接给resolve的结果干没了。分析一下执行顺序:

  • 从上往下执行,先输出1
  • 执行myPromise传入的函数,输出2
  • 执行resolve('成功了'),进入setTimeout(() => {...}),先执行当前栈的同步代码
  • 执行mp1.then(...)
  • 进入then函数
  • 由于修改PromiseState的代码被包裹在setTimeout中了,此时PromiseState并未改变,仍然是pending
  • 此时then后面的if条件都不满足了
  • 往后执行,输出3
  • 最后执行setTimeout包裹的代码,修改PromiseState值为fulfilled
  • 结束

通过以上分析,发现问题的关键在于mp1.then的执行在修改PromiseState状态之前。

因此,我们改造的思路要放在mp1.then的执行上。

开始改,resolve的结果的值赋给了PromiseResult,输出是在then方法的函数参数中。

因此我们可以想到用setTimeout来包裹then方法中的代码,让它们在同步代码执行完后再执行。

试试吧。

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        try {
            fn(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : (reason) => {
            throw reason
        } 
        if (this.PromiseState === myPromise.FULFILLED) {
            setTimeout(() => {
                onFulfilled(this.PromiseResult)
            })
        }
        if (this.PromiseState === myPromise.REJECTED) {
            setTimeout(() => {
                onRejected(this.PromiseResult)
            })
        }
    }
    catch(onError) {
        this.then(null, onError)
    }
}
console.log(1)
const mp1 = new myPromise((resolve, reject) => {
    console.log(2)
    resolve('成功了')
})
mp1.then((result) => {
    console.log(result)
})
console.log(3)

输出

1
2
3
成功了

正如结果输出的那样,成功了。

回想我们应用Promise的场景,大多数情况下,是在做异步请求的情况下

也就是resolve()是被包裹在一段异步代码中。

看看原生的例子:

console.log(1)
const p = new Promise((resolve, reject) => {
    console.log(2)
    setTimeout(() => {
        resolve('成功了!')
        console.log(4)
    })
})

p.then((result) => {
    console.log(result)
})

console.log(3)

输出

1
2
3
4
成功了

再来看看我们自己的Promise:

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        try {
            fn(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : (reason) => {
            throw reason
        } 
        if (this.PromiseState === myPromise.FULFILLED) {
            setTimeout(() => {
                onFulfilled(this.PromiseResult)
            })
        }
        if (this.PromiseState === myPromise.REJECTED) {
            setTimeout(() => {
                onRejected(this.PromiseResult)
            })
        }
    }
    catch(onError) {
        this.then(null, onError)
    }
}
console.log(1)
const mp1 = new myPromise((resolve, reject) => {
    console.log(2)
    setTimeout(() => {
        resolve('成功了')
        console.log(4)
    })
})
mp1.then((result) => {
    console.log(result)
})
console.log(3)

输出

1
2
3
4

出现了和我们上面同样的问题,简单分析一下:

  • 由于resolve('成功了')被包裹在setTimeout中NA
  • 在执行mp1.then(...)的时候,PromiseState的状态仍然是pending
  • then方法中的if都不满足条件,因此不会调用onFulfilled方法
  • 最后执行resolve('成功了')的时候,修改了PromiseState的状态,但是此时不会再去调用onFulfilled方法了

这里碰到一个比较棘手的问题:如果resolve()被包裹在异步代码中,在执行then方法的时候,PromiseState的状态始终是pending, 那么onFulfilled()和onRejected()都不会执行。

因此我们需要在then()中处理PromiseState为pending状态的情况。

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        try {
            fn(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : (reason) => {
            throw reason
        } 
        if (this.PromiseState === myPromise.PENDING) {
            ......
        }
        if (this.PromiseState === myPromise.FULFILLED) {
            setTimeout(() => {
                onFulfilled(this.PromiseResult)
            })
        }
        if (this.PromiseState === myPromise.REJECTED) {
            setTimeout(() => {
                onRejected(this.PromiseResult)
            })
        }
    }
    catch(onError) {
        this.then(null, onError)
    }
}

继续分析,我们想让onFulfilled或onRejected在PromiseState状态改变之后执行,而PromiseState是在resolve()中修改的,resolve又被包裹在一段异步代码中。

所以,在then方法中,onFulfilled()或onRejected()会在未来某个时刻执行。

在执行到then()的时候,如果PromiseState是pending状态,我们需要将onFulfilled()和onRejected()先保存起来。

保存数据通常想到的是数组,这里我们定义两个数组onFulfilledCbs和onRejectedCbs来分别存储onFulfilled()和onRejected().

在then方法中,判断如果PromiseState为pending状态,就先存储起来。

然后在resolve()和reject()中,当PromiseState为结束状态的时候,去除数组中的函数并执行。

class myPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(fn) {
        this.PromiseState = myPromise.PENDING
        this.PromiseResult = null
        this.onFulfilledCbs = []
        this.onRejectedCbs = []
        try {
            fn(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED
            this.PromiseResult = result
            this.onFulfilledCbs.forEach(cb => {
                cb(result)
            })
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED
            this.PromiseResult = reason
            this.onRejectedCbs.forEach(cb => {
                cb(reason)
            })
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : (reason) => {
            throw reason
        } 
        if (this.PromiseState === myPromise.PENDING) {
            this.onFulfilledCbs.push(onFulfilled)
            this.onRejectedCbs.push(onRejected)
        }
        if (this.PromiseState === myPromise.FULFILLED) {
            setTimeout(() => {
                onFulfilled(this.PromiseResult)
            })
        }
        if (this.PromiseState === myPromise.REJECTED) {
            setTimeout(() => {
                onRejected(this.PromiseResult)
            })
        }
    }
    catch(onError) {
        this.then(null, onError)
    }
}
console.log(1)
const mp1 = new myPromise((resolve, reject) => {
    console.log(2)
    setTimeout(() => {
        resolve('成功了')
        console.log(4)
    })
})
mp1.then((result) => {
    console.log(result)
})
console.log(3)

输出

1
2
3
成功了
4

输出和我们想要的结果一致,到这里myPromise的实现算是真正告一段落了。