手动实现Promise--基础版

1.前言

Promise是ES6发布的异步编程的一种解决方案。比传统的解决方案--回调函数和事件,更加合理和强大。它由社区提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。本文详细讲一下怎么手动实现一个Promise。阅读前请确保已经熟练使用Promise,要不会有些费劲。本文较长,如想看懂请耐心,建议边看边自己写一遍。

原生Promise请看文档:Promise

 

2.实现

2.1 认识Promise

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        const p = new Promise((resolve, reject) => {
            console.log('new Promise')
            // 这里执行异步操作,比如调接口。此处用setTimeout代替
            setTimeout(() => {
                resolve('Hello Promise')
            }, 1000)
        })
 
        p.then(res => {
            console.log('res========', res)
        }, err => {
            console.log('err============', err)
        })
    </script>
</head>
<body>
    
</body>
</html>

输出如下:

我们简单描述一下Pormsie

1. Promise是一个类,构造函数的参数是一个函数,这个函数的两个参数resolve和reject也是函数。

2. new 一个Promise的实例,在传入的函数里执行异步操作。该函数是同步任务会马上执行。成功则执行resolve函数,失败则执行reject函数。

3. Promise的实例有个then方法,then方法有两个参数。第一个是执行成功时的回调函数,函数参数是执行resolve函数时传入的值。第二个是执行失败时的回调函数,函数参数是执行reject函数时传入的值。then方法是异步调用的,在执行resolve或reject之后执行。

4. Promise的实例支持多次调用then方法,即链式调用。

 

2.2 从零开始实现Promsie

第一步首先需要一个类,为避免冲突起名为MyPromise。MyPromise的构造函数的参数是一个函数,创建MyPromise的实例时需要执行该函数。该函数有两个参数resolve和reject,这两个函数在MyPromise类中实现。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        class MyPromise {
            constructor(executor) {
                // 执行传入的函数,为入参resolve和reject绑定this
                console.log('执行传入的函数')
                executor(this._resolve.bind(this), this._reject.bind(this))
            }

            // 实现resolve函数
            _resolve() {}

            // 实现reject函数
            _reject() {}
        }

        const p = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                resolve('Hello Promise')
            }, 1000)
        })

    </script>
</head>
<body>
    
</body>
</html>

此时执行结果如下:

 

第二步实现Promise的状态。Promise对象有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败),只有异步操作的结果可以决定当前是那种状态。Promise对象的状态改变只有两种情况:

1. pending变为fulfilled

2. pending变为reject

当异步操作成功时调用执行resolve函数,把异步操作的结果(Hello Promise)传给resolve,然后在then方法的回调函数中就可以取到此值。此时状态变为fulfilled。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        class MyPromise {
            static Pending = 'pending'
            static Fulfiled = 'fulfiled'
            static Reject = 'reject'

            constructor(executor) {
                this.status = MyPromise.Pending // Promise实例的状态,初始化为pending
                this.result = undefined // result保存异步操作成功时的返回值
                this.error = undefined // error保存异步操作失败时的返回值
                // 执行传入的函数,为入参resolve和reject绑定this
                console.log('执行传入的函数')
                executor(this._resolve.bind(this), this._reject.bind(this))
            }

            // 实现resolve函数
            _resolve(result) {
                this.status = MyPromise.Fulfiled
                this.result = result
            }

            // 实现reject函数
            _reject(error) {
                this.status = MyPromise.Reject
                this.error = error
            }
        }

        const p = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                resolve('Hello Promise')
            }, 1000)
        })
    </script>
</head>
<body>
    
</body>
</html>

 

第三步实现then方法。Promise的实例有一个then方法,接受两个回调函数为参数,第一个是成功时的回调函数,第二个是失败时的回调函数。

成功的回调函数在resolve调用之后调用,接受的参数是调用resolve时传的参数,即异步操作的结果(Hello Promise),失败的回调函数亦然。

也就是说传给then方法的回调函数不会马上执行,要等状态变化之后才会执行。就是说要把回调函数存起来,等状态变化后执行。

这类似于发布订阅模式,我们用then方法注册两个回调函数,什么时候执行呢?等resolve或reject执行时执行。下面是代码和运行结果。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        class MyPromise {
            static Pending = 'pending'
            static Fulfiled = 'fulfiled'
            static Reject = 'reject'

            constructor(executor) {
                this.status = MyPromise.Pending // Promise实例的状态,初始化为pending
                this.result = undefined // result保存异步操作成功时的返回值
                this.error = undefined // error保存异步操作失败时的返回值
                this.callbacks = [] // 保存then方法传入的回调函数,在实例状态改变时调用
                // 执行传入的函数,为入参resolve和reject绑定this
                console.log('执行传入的函数')
                executor(this._resolve.bind(this), this._reject.bind(this))
            }

            // 实现resolve函数
            _resolve(result) {
                // 调用_resolve时,表示异步操作成功。
                // 此时置状态为fulfiled,把异步操作的返回结果(这里是字符串Hello Promise)赋给this.result
                // 然后调用回调函数onSuccess
                this.status = MyPromise.Fulfiled
                this.result = result
                this.callbacks.forEach((cb) => {
                    this._handle(cb)
                })
            }

            // 实现reject函数
            _reject(error) {
                this.status = MyPromise.Reject
                this.error = error
                this.callbacks.forEach((cb) => {
                    this._handle(cb)
                })
            }

            // then方法把传入的回调函数保存起来,当状态改变时调用。
            then(onSuccess, onError) {
                this.callbacks.push({ onSuccess, onError })
            }

            _handle(cb) {
                const { onSuccess, onError } = cb
                if (this.status = MyPromise.Fulfiled) {
                    onSuccess(this.result)
                }

                if (this.status === MyPromise.Reject) {
                    onError(this.error)
                }
            }
        }

        const p = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                resolve('Hello Promise')
            }, 1000)
        })

        p.then((res) => {
            console.log('res===========', res)
        }, (err) => {
            console.log('err========', err)
        })

    </script>
</head>
<body>
    
</body>
</html>

执行结果:

 

到目前为止一个基本的Promise对象就实现了。

 

第四步是实现链式调用,这是比较难理解的地方。实例的then方法后面可以再跟then方法,前一个then方法的回调函数执行完成后,会将返回结果作为参数传入下一个then方法的回调函数。

其实链式调用无非就是返回一个类的实例。Promise对象采用的是返回一个新的Promise实例。

 p.then((res) => {
    console.log('res===========', res)
    return res + 'aaa'
 }, (err) => {
    console.log('err========', err)
 }).then((res1) => {
    console.log('res1==========', res1)
 }, (err1) => {
    console.log('err1==========', err1)
 })

调用then方法返回的Promise的result值来源于当前实例的成功回调函数(then的第一个参数)的执行结果,即onSuccess执行的结果。result只能在resolve方法中被赋值,因此我们也要把onSuccess执行成功的结果通过resolve的执行来传入到下一个Promise中去。

加入链式调用的处理:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        class MyPromise {
            static Pending = 'pending'
            static Fulfiled = 'fulfiled'
            static Reject = 'reject'

            constructor(executor) {
                this.status = MyPromise.Pending // Promise实例的状态,初始化为pending
                this.result = undefined // result保存异步操作成功时的返回值
                this.error = undefined // error保存异步操作失败时的返回值
                this.callbacks = [] // 保存then方法传入的回调函数,在实例状态改变时调用
                // 执行传入的函数,为入参resolve和reject绑定this
                console.log('执行传入的函数')
                executor(this._resolve.bind(this), this._reject.bind(this))
            }

            // 实现resolve函数
            _resolve(result) {
                // 调用_resolve时,表示异步操作成功。
                // 此时置状态为fulfiled,把异步操作的返回结果(这里是字符串Hello Promise)赋给this.result
                // 然后调用回调函数onSuccess
                this.status = MyPromise.Fulfiled
                this.result = result
                this.callbacks.forEach((cb) => {
                    this._handle(cb)
                })
            }

            // 实现reject函数
            _reject(error) {
                this.status = MyPromise.Reject
                this.error = error
                this.callbacks.forEach((cb) => {
                    this._handle(cb)
                })
            }

            // then方法把传入的回调函数保存起来,当状态改变时调用。
            then(onSuccess, onError) {
                // 把下一个Promise实例的resolve方法也存进callbasks中去
                // 是为了把当前onSuccess的执行结果通过resolve方法赋给下一个Promise实例的result
                return new MyPromise((nextResolve, nextReject) => {
                    this._handle({
                        nextResolve,
                        nextReject,
                        onSuccess,
                        onError
                    })
                })
            }

            _handle(cb) {
                const {nextResolve, nextReject, onSuccess, onError } = cb

                if (this.status === MyPromise.Pending) {
                    this.callbacks.push(cb)
                }

                if (this.status === MyPromise.Fulfiled) {
                    const nextResult = onSuccess ? onSuccess(this.result) : undefined
                    nextResolve(nextResult)
                    return
                }

                if (this.status === MyPromise.Reject) {
                    const nextError = onError ? onError(this.error) : undefined
                    nextReject(nextError)
                    return
                }
            }
        }

        const p = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                resolve('Hello Promise')
            }, 1000)
        })

        // 调用第一个then方法之后,会创建下一个Promise对象。
        // 会把当前Promise对象的成功回调函数、失败回调函数、下一个Resolve方法、下一个Reject方法存起来。
        // 然后在调用onSuccess之后,把其返回值作为nextResolve的参数,赋给下一个Promise对象的result1,
        // 这样第二个then方法的成功回调函数就会取到result1,以此类推。
        p.then((res) => {
            console.log('res===========', res)
            return res + 'aaa'
        }, (err) => {
            console.log('err========', err)
        }).then((res1) => {
            console.log('res1==========', res1)
        }, (err1) => {
            console.log('err1==========', err1)
        })

    </script>
</head>
<body>
    
</body>
</html>

运行结果如下:

 

第五步再深入一点。上面只说了then方法返回一个字符串(Hello Promise)的情况,如果then方法返回一个Promise实例呢?如下:

        p.then((res) => {
            console.log('res==========', res)
            return new MyPromise((resolve, reject) => {
                setTimeout(() => {
                    resolve('Test Promise')
                }, 2000)
            })
        }, (err) => {
            console.log('err========', err)
        }).then((res1) => {
            console.log('res1==========', res1)
        }, (err1) => {
            console.log('err1==========', err1)
        })

此时第二个then方法会等待这个返回的Promise执行完毕后执行。代码处理如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
        class MyPromise {
            static Pending = 'pending'
            static Fulfiled = 'fulfiled'
            static Reject = 'reject'

            constructor(executor) {
                this.status = MyPromise.Pending // Promise实例的状态,初始化为pending
                this.result = undefined // result保存异步操作成功时的返回值
                this.error = undefined // error保存异步操作失败时的返回值
                this.callbacks = [] // 保存then方法传入的回调函数,在实例状态改变时调用
                // 执行传入的函数,为入参resolve和reject绑定this
                console.log('执行传入的函数')
                executor(this._resolve.bind(this), this._reject.bind(this))
            }

            // 实现resolve函数
            _resolve(result) {
                if (result instanceof MyPromise) {
                    // 表示onSuccess返回结果是一个Promise实例,假定这个实例为a
                    // 我们是想获取这个叫a的Promise实例里面的result,但是a实例并没有暴露任何获取result的方法
                    // 但是a实例的then方法里有个成功的回调函数,这个回调函数里可以取到result。
                    result.then(
                        this._resolve.bind(this),
                        this._reject.bind(this)
                    )
                    return
                }
                this.status = MyPromise.Fulfiled
                this.result = result
                this.callbacks.forEach((cb) => {
                    this._handle(cb)
                })
            }

            // 实现reject函数
            _reject(error) {

                if (error instanceof MyPromise) {
                    error.then(
                        this._resolve.bind(this),
                        this._reject.bind(this)
                    )
                }
                this.status = MyPromise.Reject
                this.error = error
                this.callbacks.forEach((cb) => {
                    this._handle(cb)
                })
            }

            // then方法把传入的回调函数保存起来,当状态改变时调用。
            then(onSuccess, onError) {
                // 把下一个Promise实例的resolve方法也存进callbasks中去
                // 是为了把当前onSuccess的执行结果通过resolve方法赋给下一个Promise实例的result
                return new MyPromise((nextResolve, nextReject) => {
                    this._handle({
                        nextResolve,
                        nextReject,
                        onSuccess,
                        onError
                    })
                })
            }

            _handle(cb) {
                const {nextResolve, nextReject, onSuccess, onError } = cb

                if (this.status === MyPromise.Pending) {
                    this.callbacks.push(cb)
                }

                if (this.status === MyPromise.Fulfiled) {
                    const nextResult = onSuccess ? onSuccess(this.result) : undefined
                    nextResolve(nextResult)
                    return
                }

                if (this.status === MyPromise.Reject) {
                    const nextError = onError ? onError(this.error) : undefined
                    nextReject(nextError)
                    return
                }
            }
        }

        const p = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                resolve('Hello Promise')
            }, 1000)
        })

        // 调用第一个then方法之后,会创建下一个Promise对象。
        // 会把当前Promise对象的成功回调函数、失败回调函数、下一个Resolve方法、下一个Reject方法存起来。
        // 然后在调用onSuccess之后,把其返回值作为nextResolve的参数,赋给下一个Promise对象的result1,
        // 这样第二个then方法的成功回调函数就会取到result1,以此类推。
        p.then((res) => {
            console.log('res==========', res)
            return new MyPromise((resolve, reject) => {
                setTimeout(() => {
                    resolve('Test Promise')
                }, 2000)
            })
        }, (err) => {
            console.log('err========', err)
        }).then((res1) => {
            console.log('res1==========', res1)
        }, (err1) => {
            console.log('err1==========', err1)
        })

    </script>
</head>
<body>
    
</body>
</html>

执行结果如下:

 

下一篇:手动实现Promise--进阶版,finally和catch

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jsonbro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值