进阶学习8:手写Promise源码

Posted JIZQAQ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进阶学习8:手写Promise源码相关的知识,希望对你有一定的参考价值。

目录

一、编写思路

二、源码

三、运行方式


不得不说,这部分老师讲的真的太流畅了…真的是一层一层递进讲的,从最开始实现了部分promise的基本原理,然后引入一条新的用法,再在原本代码基础上添加该用法的编写。所以记笔记的时候,我并没有完全按照课时分开记,而是一边敲源码一边记在备注里面了。

这导致了这部分可能笔记不是特别便于阅读,如果大家有什么不明白的,欢迎留言一起交流学习哈。

一、编写思路

在我后面的源码里面,每一块对应的部分都有更详细的注释,所以这边我就简单说一下自己手写Promise代码的思路。

首先我们了解到Promise有下面3个核心属性,于是先把这部分的代码编写出来。

1. Promise 就是一个类 在执行这个类的时候 需要传递一个执行器进去 执行器会立即执行

2. Promise 中有三种状态 分别为 成功 fulfilled 失败 rejected 等待 pending
    pending -> fulfilled
    pending -> rejected
    一旦状态确定就不可更改

3. resolve和reject函数是用来更改状态的
    resolve: fulfilled
    reject: rejected

然后了解到then方法,于是再把简单的then方法编写

4. then方法内部做的事情就判断状态 如果状态是成功 调用成功的回调函数 如果状态是失败 调用失败回调函数 then方法是被定义在原型对象中的

5. then成功回调有一个参数 表示成功之后的值 then失败回调有一个参数 表示失败后的原因

当中要注意如果我们promise里面传入的是异步的函数,.then()是需要等待我们异步的部分完成之后再做判断的。我在.then()函数代码那块有更详细的备注。

接下来完成下面这个功能点

6. 同一个promise对象下面的then方法是可以被调用多次的

7. then方法是可以被链式调用的, 后面then方法的回调函数拿到值的是上一个then方法的回调函数的返回值

编写完之后6、7功能点之后,需要注意处理一些异常报错,如:

1.promise对象的.then()中可以return一个promise对象,但是不能是它本身。需要在我们程序中把这个行为识别出来,报指定的错误。

2.要捕获执行器当中发生的错误,并报错

还有.then()当中不传递任何东西的使用方式,实际上.then()的写法逻辑上等同于 .then(value => value) 

最后依次实现以下功能点

8. promise.all()允许我们按照异步代码调用的顺序得到异步代码的执行结果,接收数组作为参数,里面可以是普通值也可以是promise对象,返回值也是一个promise对象;里面所有都成功才算成功,只要有一个任务失败就算失败

9. promise.resolve()里面可以给普通值或者promise对象,如果是promise对象会原封不动作为返回值,如果是普通值会包裹一层成为promise对象再传递下去,再后面用then方法来获得返回值

10.finally() 无论最后是成功还是失败,finally方法都会被执行一次,finally返回的时候promise,后面可以链式调用.then()获取值

11.catch() 用来处理当前promise对象最终为失败的情况的,then方法不传递失败回调的话,传了catch,那么失败方法会被catch捕获

二、源码

下面就直接晒出我的源码了。

MyPromise.js文件

/*
  1. Promise 就是一个类 在执行这个类的时候 需要传递一个执行器进去 执行器会立即执行
  2. Promise 中有三种状态 分别为 成功 fulfilled 失败 rejected 等待 pending
    pending -> fulfilled
    pending -> rejected
    一旦状态确定就不可更改
  3. resolve和reject函数是用来更改状态的
    resolve: fulfilled
    reject: rejected
  4. then方法内部做的事情就判断状态 如果状态是成功 调用成功的回调函数 如果状态是失败 调用失败回调函数 then方法是被定义在原型对象中的
  5. then成功回调有一个参数 表示成功之后的值 then失败回调有一个参数 表示失败后的原因
  6. 同一个promise对象下面的then方法是可以被调用多次的
  7. then方法是可以被链式调用的, 后面then方法的回调函数拿到值的是上一个then方法的回调函数的返回值
  8. promise.all()允许我们按照异步代码调用的顺序得到异步代码的执行结果,接收数组作为参数,里面可以是普通值也可以是promise对象,返回值也是一个promise对象;里面所有都成功才算成功,只要有一个任务失败就算失败
  9. promise.resolve()里面可以给普通值或者promise对象,如果是promise对象会原封不动作为返回值,如果是普通值会包裹一层成为promise对象再传递下去,再后面用then方法来获得返回值
  10.finally() 无论最后是成功还是失败,finally方法都会被执行一次,finally返回的时候promise,后面可以链式调用.then()获取值
  11.catch() 用来处理当前promise对象最终为失败的情况的,then方法不传递失败回调的话,传了catch,那么失败方法会被catch捕获
*/
const PENDING = 'pending'//等待
const FULFILLED = 'fulfilled'//成功
const REJECTED = 'rejected'//失败
class MyPromise {
    //执行器
    constructor (executor) {
        try{
            //执行器在这边立即执行,所以立刻调用它
            executor(this.resolve, this.reject)
        }catch(error){
            this.reject(error)
        }
    }
    //状态
    status = PENDING
    //成功之后的值
    value = undefined
    //失败之后的原因
    reason = undefined
    //成功回调
    // successCallBack = undefined
    successCallBack = []
    // //失败回调
    // failCallBack = undefined
    failCallBack = []
    
    //用箭头函数是为了让它内部的this指定不发生改变
    resolve = value => {
        //如果状态不是等待,阻止状态更改
        if(this.status !== PENDING) return;
        this.status = FULFILLED
        // 保存成功之后的值,then方法需要用到
        this.value = value
        // 判断成功回调是否存在,如果存在就调用
        // if(this.successCallBack !== undefined){
        //     this.successCallBack(this.value)
        // }
        while(this.successCallBack.length){
            this.successCallBack.shift()(this.value)
        }
    } 
    reject = reason => {
        if(this.status !== PENDING) return;
        this.status = REJECTED
        // 保存失败之后的原因,then方法需要用到
        this.reason = reason
        // 判断失败回调是否存在,如果存在就调用
        // if(this.failCallBack !== undefined){
        //     this.failCallBack(this.reason)
        // }
        while(this.failCallBack.length){
            this.failCallBack.shift()(this.reason)
        }
    } 
    then (successCallBack, failCallBack) {
        //如果前面的.then()括号里面为空,那么默认是.then(value => value),同样错误也是这么传递
        successCallBack = successCallBack??(value => value)
        failCallBack = failCallBack??(reason => {throw reason})
        let promise2 = new MyPromise((resolve, reject) => {
            // 判断状态
            if (this.status === FULFILLED){
                //这里添加setTimeout是为了让下面这块变成异步代码,promise2需要在完全new完之后才能或得到
                //如果不改成异步的话,resolvePromise无法获得promise2,所以延迟时间设置为0
                setTimeout(()=>{
                    try{
                        let returnValue = successCallBack(this.value)
                        /*
                        * 需要判断 returnValue 是普通值还是promise对象
                        * 如果是普通值,直接调用resolve
                        * 如果是promise对象 查看promise对象返回的结果
                        * 再根据promise对象返回的结果决定调用resolve还是reject
                        * 无论是哪个状态都需要做这个判断,所以我们直接写成一个函数
                        */
                        resolvePromise(promise2,returnValue,resolve, reject)
                    }catch(err){
                        reject(err)
                    }
                },0)
            }
            else if (this.status === REJECTED){
                setTimeout(()=>{
                    try{
                        let returnValue = failCallBack(this.reason)
                        /*
                        * 需要判断 returnValue 是普通值还是promise对象
                        * 如果是普通值,直接调用resolve
                        * 如果是promise对象 查看promise对象返回的结果
                        * 再根据promise对象返回的结果决定调用resolve还是reject
                        * 无论是哪个状态都需要做这个判断,所以我们直接写成一个函数
                        */
                        resolvePromise(promise2,returnValue,resolve, reject)
                    }catch(err){
                        reject(err)
                    }
                },0)
            }
            //处理等待状态,也就是promise里面被添加了异步逻辑
            else{
                //等待的话,将成功和失败回调存起来
                // this.successCallBack = successCallBack
                // this.failCallBack = failCallBack
                this.successCallBack.push(() =>{
                    setTimeout(()=>{
                        try{
                            let returnValue = successCallBack(this.value)
                            /*
                            * 需要判断 returnValue 是普通值还是promise对象
                            * 如果是普通值,直接调用resolve
                            * 如果是promise对象 查看promise对象返回的结果
                            * 再根据promise对象返回的结果决定调用resolve还是reject
                            * 无论是哪个状态都需要做这个判断,所以我们直接写成一个函数
                            */
                            resolvePromise(promise2,returnValue,resolve, reject)
                        }catch(err){
                            reject(err)
                        }
                    },0)
                })
                this.failCallBack.push(() =>{
                    setTimeout(()=>{
                        try{
                            let returnValue = failCallBack(this.reason)
                            /*
                            * 需要判断 returnValue 是普通值还是promise对象
                            * 如果是普通值,直接调用resolve
                            * 如果是promise对象 查看promise对象返回的结果
                            * 再根据promise对象返回的结果决定调用resolve还是reject
                            * 无论是哪个状态都需要做这个判断,所以我们直接写成一个函数
                            */
                            resolvePromise(promise2,returnValue,resolve, reject)
                        }catch(err){
                            reject(err)
                        }
                    },0)
                })
            }
        });
        return promise2;
    }
    static all (array) {
        let result = [];
        let index = 0;
        
        return new MyPromise((resolve,reject) => {
            function addData (key, value) {
                result[key] = value
                index++
                //只有index和array长度相等了,才证明所有异步操作执行完了。用这个来等待异步代码的结果
                if(index === array.length){
                    resolve(result)
                }
            }

            for(let i = 0;i<array.length;i++){
                let current = array[i]
                if(current instanceof MyPromise) {
                    //promise对象
                    current.then(value => addData(i, value),reason => reject(reason))
                }else{
                    //普通值
                    addData(i, array[i])
                }
            }
        })
    }
    static resolve (value) {
        if(value instanceof MyPromise){
            return value;
        }
        else{
            return new MyPromise(resolve => resolve(value))
        }
    }
    finally(callback) {
        //现在情况是如果我们在finally里面return了一个promise对象,异步的,并没有等待就执行完了,所以需要做一些修改
        return this.then(value =>{
            //无论是我们callback返回的是普通值还是promise都转成promise
            return MyPromise.resolve(callback()).then(() => value);
            // callback()
            // return value
        },reason =>{
            return MyPromise.resolve(callback()).then(() => {throw reason});
            // callback()
            // throw reason
        })
    }
    catch(failCallBack){
        return this.then(undefined,failCallBack)
    }
}

function resolvePromise(promise2, returnValue,resolve, reject) {
    if(promise2 === returnValue) {
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    if (returnValue instanceof MyPromise){
        //是promise对象
        // returnValue.then(value=>{
        //     resolve(value)
        // },reason=>{
        //     reject(reason)
        // })
        //简化上面写的代码为
        returnValue.then(resolve,reject);
    }else{
        // 普通值
        resolve(returnValue)
    }
}
module.exports = MyPromise

调用的index.js文件 这块因为是每个模块MyPromise代码敲完之后测试的,忘记做具体备注了,大家可以对照上面那个文件开头列的每一条想一个对应的测试样例,这个并不麻烦,就当做是普通的Promise函数使用就是了。

import MyPromise from './MyPromise'

let promise = new MyPromise((resolve, reject) => {
    //resolve('成功')
     reject('失败')
    //setTimeout(()=>resolve('成功...'),2000)
    //throw new Error('executor error')

})

// promise.then(value => {
//     console.log(value)
// }, reason => {
//     console.log(reason)
// })

// promise.then(value => {
//     console.log(value)
// }, reason => {
//     console.log(reason)
// })

// promise.then(value => {
//     console.log(value)
// }, reason => {
//     console.log(reason)
// })
function other () {
    return new MyPromise((resolve, reject) => {
        //resolve('other');
        reject('other');
    })
}

// promise.then(value => {
//     console.log(value)
//     return 100;
// },reason => {
//     console.log(reason)
//     return 100;
// }).then(value => {
//     console.log(value)
//     return other()
// },reason => {
//     console.log(reason)
//     return other()
// }).then(value => {
//     console.log(value)
// },reason => {
//     console.log(reason)
// })

// let p1 = promise.then(value => {
//     console.log(value)
//     return p1;
// })
// p1.then(value=>{
//     console.log(value);
// },reason => {
//     console.log(reason.message)
// })

// promise.then(value=>{
//     console.log(value);
//     //throw new Error('executor error')
//     return 'aaa'
// },reason => {
//     console.log(value)
//     return 10000
// }).then(value=>{
//     console.log(value);
//     //throw new Error('executor error')
// },reason => {
//     console.log(reason.message)
// })
//promise.then(value => console.log(value))


// promise.then().then().then(value => console.log(value), reason => {
//     console.log(reason)
// })
function p1 () {
    return new MyPromise(function (resolve, reject) {
        setTimeout(function () {
            resolve('p1')},2000)
    })
}
// function p2 () {
//     return new MyPromise(function (resolve, reject) {
//         resolve('p2 resolve')
//         //reject('p2 reject')
//     })
// }

//MyPromise.all(['a','b',p1(),p2(),'c']).then(result => console.log(result))

//MyPromise.resolve(100).then(value => console.log(value))
//MyPromise.resolve(p1()).then(value => console.log(value))

//这个例子现在是先打印finally,然后等待2秒打印p2 resolve,p1并不会被打印出来
// p2().finally(() => {
//     console.log('finally')
//     return p1()
// }).then(value=>{
//     console.log(value)
// },reason => {
//     console.log(reason)
// })

function p2 () {
    return new MyPromise(function (resolve, reject) {
        //resolve('p2 resolve')
        reject('p2 reject')
    })
}
p2().then(value => console.log(value)).catch(reason => console.log(reason))

三、运行方式

我使用到了webpack-dev-server运行的,如果之前没用使用过这个工具的,可以看一下我下面这篇学习笔记里面的五、Promise——4.使用Promise封装ajax函数 部分,里面有从最初的安装开始的讲解步骤。

这篇学习笔记也比较详细的讲了Promise的相关概念、属性、运行方式等等。

进阶学习6:javascript异步编程——Promise、链式调用、异常处理、静态方法、并行执行、执行时序、宏任务微任务理解

https://blog.csdn.net/qq_43106115/article/details/117193117

然后在Chrome打开对应的地址,F12观察console的输出是否和预期相符就好。

参考:拉钩网《大前端训练营》课程

以上是关于进阶学习8:手写Promise源码的主要内容,如果未能解决你的问题,请参考以下文章

手写promise进阶版本

初步了解 promise-02 简单手写源码

从 0 开始手写一个 Spring MVC 框架,向高手进阶!

手写Promise A+ 规范

手写Promise

手写Promise