万字详解JavaScript手写一个Promise

news2024/10/1 19:21:51
目录
  • 前言
  • Promise核心原理实现
    • Promise的使用分析
    • MyPromise的实现
  • 在Promise中加入异步操作
  • 实现then方法的多次调用
  • 实现then的链式调用
  • then方法链式调用识别Promise对象自返回
  • 捕获错误及 then 链式调用其他状态代码补充
    • 捕获执行器错误
    • 捕获then中的报错
    • 错误与异步状态的链式调用
  • 将then方法的参数变成可选参数
  • Promise.all方法的实现
  • Promise.resolve方法的实现
  • finally方法的实现
  • catch方法的实现
  • 完整代码

前言

手写Promise现在已经成了面试的热门内容,但在实际开发中基本都不会去手写一个Promise,但是在面试中各种手写题可能就会遇到一个手写Promise,我们可以尽量提高我们的上限,从而获取更多的工作机会。

Promise核心原理实现

首先我们从使用的角度来分析一下Promise,然后编写一个最简单版本的Promise。

Promise的使用分析

Promise就是一个类,在执行这个类的时候,需要传递一个执行器(回调函数)进去,执行器会立即执行。

Promise中的状态分为三个,分别是:

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

状态的切换只有两种,分别是:

  • pending→fulfilled
  • pending→rejected

一旦状态发生改变,就不会再次改变:

  • 执行器中的两个参数,分别是resolve和reject,其实就是两个回调函数,调用resolve是从pending状态到fulfilled,调用reject是从状态pending到rejected。传递给这两个回调函数的参数会作为成功或失败的值。
  • Promise的实例对象具有一个then方法,该方法接受两个回调函数,分别来处理成功与失败的状态,then方法内部会进行判断,然后根据当前状态确定调用的回调函数。then方法应该是被定义在原型对象中的。
  • then的回调函数中都包含一个值,如果是成功,表示成功后返回的值;如果是失败就表示失败的原因。

MyPromise的实现

根据我们上面的分析,写出如下代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

MyPromise.js

// 定义所有状态的常量

const PENDING = 'pending'

const FULFILLED = 'fulfilled'

const REJECTED = 'rejected'

// Promise实质上就是一个类,首先创建一个Promise的类

class MyPromise {

    // 实例化Promise时需要一个回调函数,该回调函数立即执行

    constructor(executor) {

        // 在调用executor需要传递两个回调函数,分别是resolve和reject

        executor(this.resolve, this.reject)

    }

    // Promise 的状态

    status = PENDING

    // 记录成功与失败的值

    value = undefined

    reason = undefined

    resolve = (value) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象

        // 形参value表示,调用resolve时传递的参数

        // 如果当前状态不是pending,就直接跳出该逻辑

        if (this.status !== PENDING) return

        // 将状态修改为成功

        this.status = FULFILLED

        // 将传入的值进行保存

        this.value = value

    }

    reject = (reason) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象

        // 形参reason表示,调用reject时传递的失败的原因

        // 如果当前状态不是pending,就直接跳出该逻辑

        if (this.status !== PENDING) return

        // 将状态修改为失败

        this.status = REJECTED

        // 保存失败的原因

        this.reason = reason

    }

    // then方法的实现

    then (onFulfilled, onRejected) {

        // 判断当前状态,根据状态调用指定回调

        if (this.status === FULFILLED) {

            // 将成功的值作为参数返回

            onFulfilled(this.value)

        } else if (this.status === REJECTED) {

            // 将失败的原因作为参数返回

            onRejected(this.reason)

        }

    }

}

// 导出Promise

module.exports = MyPromise

现在我们就来写一段代码验证一下上面的代码

验证resolve:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

const MyPromise = require('./myPromise')

let promise = new MyPromise((resolve, reject) => {

    resolve('成功')

})

promise.then(value => {

    console.log(value);

}, reason => {

    console.log(reason);

})

/* 输出

    成功

*/

验证reject:

?

1

2

3

4

5

6

7

8

9

10

11

12

const MyPromise = require('./myPromise')

let promise = new MyPromise((resolve, reject) => {

    reject('失败')

})

promise.then(value => {

    console.log(value);

}, reason => {

    console.log(reason);

})

/* 输出

    失败

*/

验证状态不可变:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

const MyPromise = require('./myPromise')

let promise = new MyPromise((resolve, reject) => {

    resolve('成功')

    reject('失败')

})

promise.then(value => {

    console.log(value);

}, reason => {

    console.log(reason);

})

/* 输出

    成功

*/

在Promise中加入异步操作

如果我们的代码中存在异步操作,我们自己写的Promise将毫无用处,

例如下面这段代码:

?

1

2

3

4

5

6

7

8

9

const MyPromise = require('./myPromise')

let promise = new MyPromise((resolve, reject) => {

    setTimeout(resolve, 2000, '成功')

})

promise.then(value => {

    console.log(value);

}, reason => {

    console.log(reason);

})

这段代码2000ms后没有任何输出,为了解决这个问题我们将对自己编写的类进行如下操作:

  • 创建两个实例方法用于存储我们传入的成功与失败的处理逻辑。
  • then方法添加状态为pending时的处理逻辑,这时将传递进来的属性保存到实例上。
  • 在成功或者失败时调用相应的传递进来的回调函数(实例属性存在函数的情况下)。

实现代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

// MyPromise.js

const PENDING = 'pending'

const FULFILLED = 'fulfilled'

const REJECTED = 'rejected'

class MyPromise {

    constructor(executor) {

        executor(this.resolve, this.reject)

    }

    status = PENDING

    value = undefined

    reason = undefined

    // 存储成功与失败的处理逻辑

    onFulfilled = undefined

    onRejected = undefined

    resolve = (value) => {

        if (this.status !== PENDING) return

        this.status = FULFILLED

        this.value = value

        // 如果将状态修复为成功,调用成功的回调

        this.onFulfilled && this.onFulfilled(this.value)

    }

    reject = (reason) => {

        if (this.status !== PENDING) return

        this.status = REJECTED

        this.reason = reason

        // 如果将状态修复为失败,调用失败的回调

        this.onRejected && this.onRejected(this.reason)

    }

    then (onFulfilled, onRejected) {

        if (this.status === FULFILLED) {

            onFulfilled(this.value)

        } else if (this.status === REJECTED) {

            onRejected(this.reason)

        } else {

            // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调

            this.onFulfilled = onFulfilled

            this.onRejected = onRejected

        }

    }

}

module.exports = MyPromise

这里的this.onFulfilled && this.onFulfilled(this.value)表示如果该属性存在就调用这个方法。

现在我们重新执行一开始上面那一段代码,2s后会成功输出成功

实现then方法的多次调用

Promise实例中存在要给then方法,允许我们在Promise实例中链式调用,每个then方法还会返回一个Promise实例,

如下图所示:

Promise实例方法then是可以多次进行调用,而我们现在自己封装的却执行调用一次,现在根据新需要来重新改写我们的代码,

实现思路如下:

  • 定义可以存储多个回调的数组,用于存储多个回调函数。
  • 如果是同步执行的代码,执行后立即知道执行结果,所以可以直接调用回调函数。
  • 如果是异步代码,需要将每次回调函数保存到数组中,然后状态变化时依次调用函数。

实现代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

// MyPromise.js

const PENDING = 'pending'

const FULFILLED = 'fulfilled'

const REJECTED = 'rejected'

class MyPromise {

    constructor(executor) {

        executor(this.resolve, this.reject)

    }

    status = PENDING

    value = undefined

    reason = undefined

    // 存储成功与失败的处理逻辑

    onFulfilled = []

    onRejected = []

    resolve = (value) => {

        if (this.status !== PENDING) return

        this.status = FULFILLED

        this.value = value

        // 如果将状态修复为成功,调用成功的回调

        while (this.onFulfilled.length) {

            // Array.prototype.shift() 用于删除数组第一个元素,并返回

            this.onFulfilled.shift()(this.value)

        }

    }

    reject = (reason) => {

        if (this.status !== PENDING) return

        this.status = REJECTED

        this.reason = reason

        // 如果将状态修复为失败,调用失败的回调

        while (this.onRejected.length) {

            // Array.prototype.shift() 用于删除数组第一个元素,并返回

            this.onRejected.shift()(this.reason)

        }

    }

    then (onFulfilled, onRejected) {

        if (this.status === FULFILLED) {

            onFulfilled(this.value)

        } else if (this.status === REJECTED) {

            onRejected(this.reason)

        } else {

            // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调

            this.onFulfilled.push(onFulfilled)

            this.onRejected.push(onRejected)

        }

    }

}

module.exports = MyPromise

这里我们通过数组的shift()方法,该方法删除数组的第一个元素,并返回,返回的这个值正好是一个回调函数,然后调用该函数即可实现该功能。

实现then的链式调用

想要实现then的链式调用,主要解决两个问题:

  • 返回的是一个新的MyPormise实例。
  • then的返回值作为下一次的链式调用的参数。

这里分为两种情况:

  • 直接返回一个值,可以直接作为值使用
  • 返回一个新的MyPormise实例,此时就需要判断其状态

实现代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

// MyPromise.js

/* 省略的代码同上 */

class MyPromise {

    /* 省略的代码同上 */

    // then方法的实现

    then (onFulfilled, onRejected) {

        // then 方法返回一个MyPromise实例

        return new MyPromise((resolve, reject) => {

            // 判断当前状态,根据状态调用指定回调

            if (this.status === FULFILLED) {

                // 将成功的值作为参数返回

                // 保存执行回调函数的结果

                const result = onFulfilled(this.value)

                // 如果返回的是一个普通的值,直接调用resolve

                // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject

                resolvePromise(result, resolve, reject)

            } else if (this.status === REJECTED) {

                // 将失败的原因作为参数返回

                onRejected(this.reason)

            } else {

                // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调

                this.onFulfilled.push(onFulfilled)

                this.onRejected.push(onRejected)

            }

        })

    }

}

function resolvePromise (result, resolve, reject) {

    // 判断传递的result是不是MyPromise的实例对象

    if (result instanceof MyPromise) {

        // 说明是MyPromise的实例对象

        // 调用.then方法,然后在回调函数中获取到具体的值,然后调用具体的回调

        // result.then(value => resolve(value), reason => reject(reason))

        // 简写

        result.then(resolve, reject)

    } else {

        resolve(result)

    }

}

module.exports = MyPromise

then方法链式调用识别Promise对象自返回

在Promise中,如果then方法返回的是自己的promise对象,则会发生promise的嵌套,这个时候程序会报错,

测试代码如下:

?

1

2

3

4

5

6

7

8

9

var promise = new Promise((resolve, reject) => {

  resolve(100)

})

var p1 = promise.then(value => {

  console.log(value)

  return p1

})

// 100

// Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

想要解决这个问题其实我们只需要在then方法返回的MyPromise实例对象与then中回调函数返回的值进行比对,如果相同的返回一个reject的MyPromise实例对象,并创建一个TypeError类型的Error。

实现代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

// MyPromise.js

    /* 省略的代码同上 */

    then (onFulfilled, onRejected) {

        // then 方法返回一个MyPromise实例

        const promise = new MyPromise((resolve, reject) => {

            // 判断当前状态,根据状态调用指定回调

            if (this.status === FULFILLED) {

                // 这里并不需要延迟执行,而是通过setTimeout将其变成异步函数

                // 如果不变成异步的话是在函数内获取不到promise的

                setTimeout(() => {

                    // 将成功的值作为参数返回

                    // 保存执行回调函数的结果

                    const result = onFulfilled(this.value)

                    // 如果返回的是一个普通的值,直接调用resolve

                    // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject

                    resolvePromise(promise, result, resolve, reject)

                }, 0)

            } else if (this.status === REJECTED) {

                // 将失败的原因作为参数返回

                onRejected(this.reason)

            } else {

                // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调

                this.onFulfilled.push(onFulfilled)

                this.onRejected.push(onRejected)

            }

        })

        return promise

    }

}

function resolvePromise (promise, result, resolve, reject) {

    // 这里修改一下该函数,如果return的Promise实例对象,也就是传入的promise===result的话,说明在promise中return的是当前promise对象。

    if (promise === result) {

        // 这里调用reject,并抛出一个Error

        // return 是必须的,阻止程序向下执行

        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))

    }

    // 判断传递的result是不是MyPromise的实例对象

    if (result instanceof MyPromise) {

        // 说明是MyPromise的实例对象

        // 调用.then方法,然后在回调函数中获取到具体的值,然后调用具体的回调

        // result.then(value => resolve(value), reason => reject(reason))

        // 简写

        result.then(resolve, reject)

    } else {

        resolve(result)

    }

}

module.exports = MyPromise

这里then方法中的setTimeout的作用并不是延迟执行,而是为了调用resolvePromise函数时,保证创建的promise存在。

捕获错误及 then 链式调用其他状态代码补充

到目前为止我们现实的Promise并没有对异常做任何处理,为了保证代码的健壮性,我们需要对异常做一些处理。

捕获执行器错误

现在我们需要对执行器进行异常捕获,如果发生异常,就将我们的状态修改为rejected

捕获执行器的错误也比较简单,只需要在构造函数中加入try...catch语句就可以,

实现代码如下:

?

1

2

3

4

5

6

7

8

9

constructor(executor) {

    try {

        // 在调用executor需要传递两个回调函数,分别是resolve和reject

        executor(this.resolve, this.reject)

    } catch (e) {

        // 发生异常调用reject

        this.reject(e)

    }

}

测试代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

const MyPromise = require('./myPromise')

let promise = new MyPromise((resolve, reject) => {

    throw new Error('执行器错误')

})

promise.then(value => {

    console.log(value);

}, error => {

    console.log(error.message);

})

/* 输出

    执行器错误

*/

捕获then中的报错

现在我们需要对then中的异常捕获到,并在下一次链式调用中传递到then的第二个函数中,实现的方式也是通过try...catch语句,

示例代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

// then方法的实现

then (onFulfilled, onRejected) {

    // then 方法返回一个MyPromise实例

    const promise = new MyPromise((resolve, reject) => {

        // 判断当前状态,根据状态调用指定回调

        if (this.status === FULFILLED) {

            // 这里并不需要延迟执行,而是通过setTimeout将其变成异步函数

            // 如果不变成异步的话是在函数内获取不到promise的

            setTimeout(() => {

                try {

                    // 将成功的值作为参数返回

                    // 保存执行回调函数的结果

                    const result = onFulfilled(this.value)

                    // 如果返回的是一个普通的值,直接调用resolve

                    // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject

                    resolvePromise(promise, result, resolve, reject)

                } catch (error) {

                    reject(error)

                }

            }, 0)

        } else if (this.status === REJECTED) {

            // 将失败的原因作为参数返回

            onRejected(this.reason)

        } else {

            // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调

            this.onFulfilled.push(onFulfilled)

            this.onRejected.push(onRejected)

        }

    })

    return promise

}

测试代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

const MyPromise = require('./myPromise')

let promise = new MyPromise((resolve, reject) => {

    resolve('成功')

})

// 第一个then方法中的错误要在第二个then方法中捕获到

promise.then(value => {

    console.log('resolve', value)

    throw new Error('then的执行过程中遇到异常')

}).then(null, reason => {

    console.log(reason.message)

})

/* 输出

    resolve 成功

    then的执行过程中遇到异常

*/

错误与异步状态的链式调用

现在只对成功状态的then进行的链式调用以及错误处理,错误与异步状态未进行处理,其实处理起来也是一样的,

示例代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

// then方法的实现

then (onFulfilled, onRejected) {

    // then 方法返回一个MyPromise实例

    const promise = new MyPromise((resolve, reject) => {

        // 判断当前状态,根据状态调用指定回调

        if (this.status === FULFILLED) {

            // 这里并不需要延迟执行,而是通过setTimeout将其变成异步函数

            // 如果不变成异步的话是在函数内获取不到promise的

            setTimeout(() => {

                try {

                    // 将成功的值作为参数返回

                    // 保存执行回调函数的结果

                    const result = onFulfilled(this.value)

                    // 如果返回的是一个普通的值,直接调用resolve

                    // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject

                    resolvePromise(promise, result, resolve, reject)

                } catch (error) {

                    reject(error)

                }

            }, 0)

        } else if (this.status === REJECTED) {

            // 失败的处理同成功处理,只是调用的回调函数不同

            setTimeout(() => {

                try {

                    const result = onRejected(this.reason)

                    resolvePromise(promise, result, resolve, reject)

                } catch (error) {

                    reject(error)

                }

            }, 0)

        } else {

            this.onFulfilled.push((value) => {

                setTimeout(() => {

                    try {

                        const result = onFulfilled(value)

                        resolvePromise(promise, result, resolve, reject)

                    } catch (error) {

                        reject(error)

                    }

                }, 0)

            })

            this.onRejected.push((reason) => {

                setTimeout(() => {

                    try {

                        const result = onRejected(reason)

                        resolvePromise(promise, result, resolve, reject)

                    } catch (error) {

                        reject(error)

                    }

                }, 0)

            })

        }

    })

    return promise

}

测试代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

const MyPromise = require('./myPromise')

let promise = new MyPromise((resolve, reject) => {

    setTimeout(resolve, 2000, '成功')

})

// 第一个then方法中的错误要在第二个then方法中捕获到

promise.then(value => {

    console.log('resolve', value)

    throw new Error('then的执行过程中遇到异常')

}).then(null, reason => {

    console.log(reason.message)

})

/* 输出

    resolve 成功

    then的执行过程中遇到异常

*/

将then方法的参数变成可选参数

Promise中的then方法其实是两个可以可选参数,如果我们不传递任何参数的话,里面的结果是向下传递的,直到捕获为止,

例如下面这段代码:

?

1

2

3

4

5

6

7

8

new Promise((resolve, reject) => {

    resolve(100)

})

    .then()

    .then()

    .then()

    .then(value => console.log(value))

// 最后一个then输入100

这段代码可以理解为:

?

1

2

3

4

5

6

7

new Promise((resolve, reject) => {

    resolve(100)

})

    .then(value => value)

    .then(value => value)

    .then(value => value)

    .then(value => console.log(value))

所以说我们只需要在没有传递回调函数时,赋值一个默认的回调函数即可。

实现代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// then方法的实现

then (onFulfilled, onRejected) {

    // 如果传递函数,就是用传递的函数,否则指定一个默认值,用于参数传递

    onFulfilled = onFulfilled ? onFulfilled : value => value

    // 同理

    onRejected = onRejected ? onRejected : reason => { throw reason }

    // then 方法返回一个MyPromise实例

    const promise = new MyPromise((resolve, reject) => {

        // 判断当前状态,根据状态调用指定回调

        if (this.status === FULFILLED) {...

        } else if (this.status === REJECTED) {...

        } else {...

        }

    })

    return promise

}

Promise.all方法的实现

关于all()方法的使用,可以参数Promise.all()。简单的说Promise.all()会将多个Promise实例包装为一个Promise实例,且顺序与调用顺序一致,

示例代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

function p1 () {

    return new Promise((resolve, reject) => {

        setTimeout(() => {

            resolve('p1')

        }, 2000)

    })

}

function p2 () {

    return new Promise((resolve, reject) => {

        setTimeout(() => {

            resolve('p2')

        }, 0)

    })

}

Promise.all(['a', 'b', p1(), p2(), 'c']).then(result => {

    console.log(result)

    // ["a", "b", "p1", "p2", "c"]

})

在这段代码中,我们的p1的执行是延迟了2s的,这里如果不使用Promise.all()的话最终顺序是与我们调用不同的。

现在我们来分析一下all()的实现思路:

  • all()方法可以通过类直接调用,所以是一个静态方法
  • all()方法接收一个数组,数组中的值可以是一个普通值,也可以是一个MyPromise的实例对象
  • return一个新的MyPromise实例对象
  • 遍历数组中的每一个值,判断值得类型,如果是一个普通值得话直接将值存入一个数组;如果是一个MyPromise的实例对象的话,会调用then方法,然后根据执行后的状态,如果失败的话调用新的MyPromise实例对象中的rejecte,如果是成功话将这个值存入一个数组
  • 存入数组时计数,如果存入的数量达到传入的数组长度,说明调用完毕,执行resolve并将最终的结果数组作为参数返回。

实现代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

/**

 * @description: 将多个Promise实例合并为一个Promise实例

 * @param {*} array Promise或者普通值

 * @returns {Promise}

 */

static all (array) {

    // 用于存放最终结果的数组

    let result = []

    // 用于计算当前已经执行完的实例的数量

    let count = 0

    // 最后返回的是一个Promise实例

    return new MyPromise((resolve, reject) => {

        /**

         * @description: 将执行完毕的值加入结果数组,并根据实际情况决定是否调用resolve

         * @param {*} result 存放结果的数组

         * @param {*} index 要加入的索引

         * @param {*} value 要加入数组的值

         * @param {*} resolve Promise中的resolve

         */

        function addResult (result, index, value, resolve) {

            // 根据索引值,将结果堆入数组中

            result[index] = value

            // 执行完毕一个 count+1,如果当前值等于总长度的话说明已经执行结束了,可以直接调用resolve,说明已经成功执行完毕了

            if (++count === array.length) {

                // 将执行结果返回

                resolve(result)

            }

        }

        // 遍历穿入的数组,每个都执行then方法,获取到最终的结果

        array.forEach((p, index) => {

            // 判断p是不是MyPromise的实例,如果是的话调用then方法,不是直接将值加入数组中

            if (p instanceof MyPromise) {

                p.then(

                    // 成功时将结果直接加入数组中

                    value => {

                        addResult(result, index, value, resolve)

                    },

                    // 如果失败直接返回失败原因

                    reason => {

                        reject(reason)

                    }

                )

            }

            else {

                addResult(result, index, p, resolve)

            }

        })

    })

}

Promise.resolve方法的实现

关于Promise.resolve()方法的用法可以参考Promise.resolve()Promise.reject()

我们实现的思路主要如下:

  • 该方法是一个静态方法
  • 该方法接受的如果是一个值就将该值包装为一个MyPromise的实例对象返回,如果是一个MyPromise的实例对象,调用then方法返回。

实现代码如下:

?

1

2

3

4

5

6

7

8

static resolve (value) {

    // 如果是MyPromise的实例,就直接返回这个实例

    if (value instanceof MyPromise) return value

    // 如果不是的话创建一个MyPromise实例,并返回传递的值

    return new MyPromise((resolve) => {

        resolve(value)

    })

}

finally方法的实现

关于finally方法可参考finally()实现该方法的实现代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

finally (callback) {

    // 如何拿到当前的promise的状态,使用then方法,而且不管怎样都返回callback

    // 而且then方法就是返回一个promise对象,那么我们直接返回then方法调用之后的结果即可

    // 我们需要在回调之后拿到成功的回调,所以需要把value也return

    // 失败的回调也抛出原因

    // 如果callback是一个异步的promise对象,我们还需要等待其执行完毕,所以需要用到静态方法resolve

    return this.then(value => {

        // 把callback调用之后返回的promise传递过去,并且执行promise,且在成功之后返回value

        return MyPromise.resolve(callback()).then(() => value)

    }, reason => {

        // 失败之后调用的then方法,然后把失败的原因返回出去。

        return MyPromise.resolve(callback()).then(() => { throw reason })

    })

}

测试:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

function p1 () {

    return new MyPromise((resolve, reject) => {

        setTimeout(() => {

            resolve('p1')

        }, 2000)

    })

}

function p2 () {

    return new MyPromise((resolve, reject) => {

        reject('p2 reject')

    })

}

p2().finally(

    () => {

        console.log('finally p2')

        return p1()

    }

).then(

    value => {

        console.log(value)

    }, reason => {

        console.log(reason)

    }

)

// finally p2

// 两秒之后执行p2 reject

catch方法的实现

关于catch方法可以参考catch(),实现该方法其实非常简单,只需要在内部调用then方法,不传递第一个回调函数即可,

实现代码如下:

?

1

2

3

catch (callback) {

    return this.then(null, failCallback)

}

测试如下:

?

1

2

3

4

5

6

7

8

9

10

function p () {

    return new MyPromise((resolve, reject) => {

        reject(new Error('reject'))

    })

}

p()

    .then(value => {

        console.log(value)

    })

    .catch(reason => console.log(reason))

完整代码

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

// MyPromise.js

// 定义所有状态的常量

const PENDING = 'pending'

const FULFILLED = 'fulfilled'

const REJECTED = 'rejected'

// Promise实质上就是一个类,首先创建一个Promise的类

class MyPromise {

    // 实例化Promise时需要一个回调函数,该回调函数立即执行

    constructor(executor) {

        try {

            // 在调用executor需要传递两个回调函数,分别是resolve和reject

            executor(this.resolve, this.reject)

        } catch (e) {

            // 发生异常调用reject

            this.reject(e)

        }

    }

    // Promise 的状态

    status = PENDING

    // 记录成功与失败的值

    value = undefined

    reason = undefined

    // 存储成功与失败的处理逻辑

    onFulfilled = []

    onRejected = []

    resolve = (value) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象

        // 形参value表示,调用resolve时传递的参数

        // 如果当前状态不是pending,就直接跳出该逻辑

        if (this.status !== PENDING) return

        // 将状态修改为成功

        this.status = FULFILLED

        // 将传入的值进行保存

        this.value = value

        // 如果将状态修复为成功,调用成功的回调

        // this.onFulfilled && this.onFulfilled(this.value)

        while (this.onFulfilled.length) {

            // Array.prototype.shift() 用于删除数组第一个元素,并返回

            this.onFulfilled.shift()(this.value)

        }

    }

    reject = (reason) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象

        // 形参reason表示,调用reject时传递的失败的原因

        // 如果当前状态不是pending,就直接跳出该逻辑

        if (this.status !== PENDING) return

        // 将状态修改为失败

        this.status = REJECTED

        // 保存失败的原因

        this.reason = reason

        // 如果将状态修复为失败,调用失败的回调

        // this.onRejected && this.onRejected(this.reason)

        while (this.onRejected.length) {

            // Array.prototype.shift() 用于删除数组第一个元素,并返回

            this.onRejected.shift()(this.reason)

        }

    }

    // then方法的实现

    then (onFulfilled, onRejected) {

        // 如果传递函数,就是用传递的函数,否则指定一个默认值,用于参数传递

        onFulfilled = onFulfilled ? onFulfilled : value => value

        // 同理

        onRejected = onRejected ? onRejected : reason => { throw reason }

        // then 方法返回一个MyPromise实例

        const promise = new MyPromise((resolve, reject) => {

            // 判断当前状态,根据状态调用指定回调

            if (this.status === FULFILLED) {

                // 这里并不需要延迟执行,而是通过setTimeout将其变成异步函数

                // 如果不变成异步的话是在函数内获取不到promise的

                setTimeout(() => {

                    try {

                        // 将成功的值作为参数返回

                        // 保存执行回调函数的结果

                        const result = onFulfilled(this.value)

                        // 如果返回的是一个普通的值,直接调用resolve

                        // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject

                        resolvePromise(promise, result, resolve, reject)

                    } catch (error) {

                        reject(error)

                    }

                }, 0)

            } else if (this.status === REJECTED) {

                // 失败的处理同成功处理,只是调用的回调函数不同

                setTimeout(() => {

                    try {

                        const result = onRejected(this.reason)

                        resolvePromise(promise, result, resolve, reject)

                    } catch (error) {

                        reject(error)

                    }

                }, 0)

            } else {

                // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调

                // this.onFulfilled.push(onFulfilled)

                // this.onRejected.push(onRejected)

                this.onFulfilled.push((value) => {

                    setTimeout(() => {

                        try {

                            const result = onFulfilled(value)

                            resolvePromise(promise, result, resolve, reject)

                        } catch (error) {

                            reject(error)

                        }

                    }, 0)

                })

                this.onRejected.push((reason) => {

                    setTimeout(() => {

                        try {

                            const result = onRejected(reason)

                            resolvePromise(promise, result, resolve, reject)

                        } catch (error) {

                            reject(error)

                        }

                    }, 0)

                })

            }

        })

        return promise

    }

    catch (callback) {

        return this.then(null, callback)

    }

    finally (callback) {

        // 如何拿到当前的promise的状态,使用then方法,而且不管怎样都返回callback

        // 而且then方法就是返回一个promise对象,那么我们直接返回then方法调用之后的结果即可

        // 我们需要在回调之后拿到成功的回调,所以需要把value也return

        // 失败的回调也抛出原因

        // 如果callback是一个异步的promise对象,我们还需要等待其执行完毕,所以需要用到静态方法resolve

        return this.then(value => {

            // 把callback调用之后返回的promise传递过去,并且执行promise,且在成功之后返回value

            return MyPromise.resolve(callback()).then(() => value)

        }, reason => {

            // 失败之后调用的then方法,然后把失败的原因返回出去。

            return MyPromise.resolve(callback()).then(() => { throw reason })

        })

    }

    /**

     * @description: 将多个Promise实例合并为一个Promise实例

     * @param {*} array Promise或者普通值

     * @returns {Promise}

     */

    static all (array) {

        // 用于存放最终结果的数组

        let result = []

        // 用于计算当前已经执行完的实例的数量

        let count = 0

        // 最后返回的是一个Promise实例

        return new MyPromise((resolve, reject) => {

            /**

             * @description: 将执行完毕的值加入结果数组,并根据实际情况决定是否调用resolve

             * @param {*} result 存放结果的数组

             * @param {*} index 要加入的索引

             * @param {*} value 要加入数组的值

             * @param {*} resolve Promise中的resolve

             */

            function addResult (result, index, value, resolve) {

                // 根据索引值,将结果堆入数组中

                result[index] = value

                // 执行完毕一个 count+1,如果当前值等于总长度的话说明已经执行结束了,可以直接调用resolve,说明已经成功执行完毕了

                if (++count === array.length) {

                    // 将执行结果返回

                    resolve(result)

                }

            }

            // 遍历穿入的数组,每个都执行then方法,获取到最终的结果

            array.forEach((p, index) => {

                // 判断p是不是MyPromise的实例,如果是的话调用then方法,不是直接将值加入数组中

                if (p instanceof MyPromise) {

                    p.then(

                        // 成功时将结果直接加入数组中

                        value => {

                            addResult(result, index, value, resolve)

                        },

                        // 如果失败直接返回失败原因

                        reason => {

                            reject(reason)

                        }

                    )

                }

                else {

                    addResult(result, index, p, resolve)

                }

            })

        })

    }

    static resolve (value) {

        // 如果是MyPromise的实例,就直接返回这个实例

        if (value instanceof MyPromise) return value

        // 如果不是的话创建一个MyPromise实例,并返回传递的值

        return new MyPromise((resolve) => {

            resolve(value)

        })

    }

}

function resolvePromise (promise, result, resolve, reject) {

    // 这里修改一下该函数,如果return的Promise实例对象,也就是传入的promise===result的话,说明在promise中return的是当前promise对象。

    if (promise === result) {

        // 这里调用reject,并抛出一个Error

        // return 是必须的,阻止程序向下执行

        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))

    }

    // 判断传递的result是不是MyPromise的实例对象

    if (result instanceof MyPromise) {

        // 说明是MyPromise的实例对象

        // 调用.then方法,然后在回调函数中获取到具体的值,然后调用具体的回调

        // result.then(value => resolve(value), reason => reject(reason))

        // 简写

        result.then(resolve, reject)

    } else {

        resolve(result)

    }

}

module.exports = MyPromise

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/686858.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

硬盘设备出现“设备硬件出现致命错误,导致请求失败”怎么办?

当我们尝试访问或打开计算机上的硬盘设备&#xff0c;有时候会出现“设备硬件出现致命错误&#xff0c;导致请求失败”的错误提示&#xff0c;这该怎么办呢&#xff1f;下面我们就来了解一下。 出现“设备硬件出现致命错误&#xff0c;导致请求失败”错误的原因有哪些&#xff…

机器学习之SVM支持向量机

目录 经典SVM 软间隔SVM 核SVM SVM分类器应用于人脸识别 SVM优点 SVM缺点 经典SVM 支持向量机&#xff08;Support Vector Machine&#xff0c;SVM&#xff09;是一种二分类模型&#xff0c;其基本思想是在特征空间中找到一个最优的超平面&#xff0c;使得正负样本点到…

数据结构 队列(C语言实现)

绪论 任其事必图其效&#xff1b;欲责其效&#xff0c;必尽其方。——欧阳修&#xff1b;本篇文章主要写的是什么是队列、以及队列是由什么组成的和这些组成接口的代码实现过程。&#xff08;大多细节的实现过程以注释的方式展示请注意查看&#xff09; 话不多说安全带系好&…

Python3,关于请求重试,这次requests库给安排的明明白白。

requests库重试请求 1、引言2、requests库2.1 安装2.2 代码实例2.2.1 重试次数设置2.2.2 重试条件设置2.2.3 超时时间设置 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 你看这是啥&#xff1f; 小鱼&#xff1a;我瞅瞅… 小屌丝&#xff1a;鱼哥&#xff0c;你这眼神…

【计算机视觉】Fast Segment Anything 安装步骤和示例代码解读(含源代码)

文章目录 一、导读二、安装步骤2.1 将存储库克隆到本地2.2 创建 conda 环境2.3 安装软件包2.4 安装 CLIP2.5 下载权重文件2.6 开始使用2.6.1 Everything mode2.6.2 Text prompt2.6.3 Box prompt (xywh)2.6.4 Points prompt 三、示例代码 一、导读 论文地址&#xff1a; https:…

服务器配置与操作

服务器配置与操作 一、连接远程服务器 推荐用xshell 或者 finalshell 或者 winSCP 或者 FileZilla xshell下载地址&#xff1a;https://xshell.en.softonic.com/ 二、服务器配置 2.1 安装JDK 2.1 方法一&#xff1a;在线安装 yum list java* yum -y install java-1.8.0-ope…

【Django | 爬虫 】收集某吧评论集成舆情监控(附源码)

&#x1f935;‍♂️ 个人主页: 计算机魔术师 &#x1f468;‍&#x1f4bb; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 文章目录 一、爬取帖子、二级评论二、构建数据表三、并入项目1. spider代码2. view视图代码3. 优化后台界面3. urls路由 四、定…

第二十三章Java二维数组详解

一、创建二维数组 在 Java 中二维数组被看作数组的数组&#xff0c;即二维数组为一个特殊的一维数组&#xff0c;其每个元素又是一个一维数组。Java 并不直接支持二维数组&#xff0c;但是允许定义数组元素是一维数组的一维数组&#xff0c;以达到同样的效果。声明二维数组的语…

编程规范-控制流程、错误和异常处理

前言&#xff1a; \textcolor{Green}{前言&#xff1a;} 前言&#xff1a; &#x1f49e;这个专栏就专门来记录一下寒假参加的第五期字节跳动训练营 &#x1f49e;从这个专栏里面可以迅速获得Go的知识 今天的笔记是对编程规范的补充&#xff0c;对控制流程、错误和异常处理进行…

Ansys Zemax | 内窥镜物镜系统初始结构的优化提升(下)

系统性能提升 根据上篇的内窥镜系统分析&#xff0c;我们可以从四个方面对内窥镜物镜系统进行优化&#xff1a;元件间距、圆锥系数、MTF 值以及畸变值。点击优化-评价函数编辑器以设置具体的评价函数。&#xff08;联系我们获取文章附件&#xff09; 首先&#xff0c;用三个 CO…

NXP i.MX 8M Plus工业开发板硬件说明书--下册( 四核ARM Cortex-A53 + 单核ARM Cortex-M7,主频1.6GHz)

前 言 本文档主要介绍创龙科技TLIMX8MP-EVM评估板硬件接口资源以及设计注意事项等内容。 创龙科技TLIMX8MP-EVM是一款基于NXP i.MX 8M Plus的四核ARM Cortex-A53 单核ARM Cortex-M7异构多核处理器设计的高性能工业评估板&#xff0c;由核心板和评估底板组成。ARM Cortex-A5…

【AndroidUI设计】Bottom Navigation Activity中Fragment(碎片)的添加和下层导航图标的修改

文章目录 一、引言二、设计1、添加Fragment&#xff08;1&#xff09;确认需求&#xff08;2&#xff09;创建 <1> 方法一&#xff1a;借助工具快速生成 <2> 方法二&#xff1a;视图&#xff08;图层&#xff09;工具 <3> 方法三&#xff1a;手动…

知网G4《语数外学习》简介及投稿邮箱

知网G4教育专刊《语数外学习》简介及投稿邮箱 《语数外学习》全新改版&#xff0c;分别针对初中三个不同年级&#xff0c;每本仍然兼顾语数外三个学科。改版后的《语数外学习》将密切关注课改和中考改革的进程&#xff0c;与教材同步&#xff0c;在帮中学生朋友释疑疑惑、提高…

DOTA-PEG3-azide,1428146-79-5,DOTA三聚乙二醇叠氮,试剂相关研究说明

DOTA-PEG3-azide&#xff0c;DOTA PEG3 N3&#xff0c;DOTA三聚乙二醇叠氮产品结构式&#xff1a; 产品规格&#xff1a; 1.CAS号&#xff1a;1428146-79-5 2.分子式&#xff1a;C24H44N8O10 3.分子量&#xff1a;604.66 4.包装规格&#xff1a;白色固体 &#xff0c;1g、5g、1…

数据库性能测试

目录 前言&#xff1a; 1.引入数据库驱动包 2.添加数据库配置元件 3、JDBCRequest参数化 4、Variablesnames参数使用方法&#xff1a; 前言&#xff1a; 数据库性能测试是测试数据库系统在各种条件下的性能和稳定性的过程。它可以帮助测试人员识别数据库系统的性能瓶颈&a…

30余名「实在RPA·数字员工」在纳爱斯诞生,在618中服务千万消费者!

积水成渊&#xff0c;聚沙成塔&#xff01;谁在世界数字化大势中不断变革自己&#xff1f; 长期蝉联“中国品牌价值评价”日化行业首位&#xff0c;问鼎中国工业“奥斯卡”大奖的“大国品牌”纳爱斯——当仁不让&#xff01; 纳爱斯是日化行业领军企业&#xff0c;业务覆盖家…

SpringBoot整合MybatisPlus 自动生成controller、mapper、entity、service

首先创建SpringBoot项目 选择依赖 把application的后缀改为.yml&#xff0c;方便些。 pom.xml&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w…

一次完整的性能测试,测试人员需要做什么

目录 前言&#xff1a; 一、 规范性能测试实施流程的意义 二、 性能测试实施流程 1. 需求调研阶段 2. 测试准备阶段 3. 测试执行阶段 4. 测试报告阶段 5. 测试总结阶段 前言&#xff1a; 进行一次完整的性能测试需要经过多个阶段&#xff0c;包括需求分析、测试计划编…

OpenStack(3)--vxlan网络实战

目录 一、ML2配置文件 二、上传cirros镜像 三、创建vxlan10网络 四、创建实例/同vxlan通信测试 五、不同vxlan通信测试 5.1 新建vxlan11 5.2 新建路由/添加路由接口 5.3 不同vxlan通信测试 5.4 qemu-vnc报错 六、深度剖析vxlan 七、认识 Bridge br-ex、Bridge br-in…

「一本通 3.2 练习 6」汽车加油行驶

目录 第一步&#xff0c;二维转一维&#xff08;此步仅为方便&#xff0c;可以省略&#xff09; 第二步&#xff0c;建边&#xff08;啥都行&#xff0c;只要死不了&#xff09; 第三部&#xff0c;bfs&#xff08;你要dfs也行&#xff09; 第一步 第二步 第三步 可CA呢…