Promise原理与实现

Promise原理与实现

发表于 2024-08-12
更新于 2024-08-24
阅读时长:20分钟
阅读量:27
AI 总结
|

Promise是JavaScript中用于处理异步操作的一个关键概念。它代表了一个尚未完成但预期在将来完成的操作

1.1 基本用法

Promise是一个类,可以翻译成承诺、许诺 、期约

1、executor

Promise的参数是一个executor函数,会在promise被调用时,立即执行

而executor也有两个参数,分别是reoslve函数、reject函数

// 这个传入的函数,称为executor
const promise = new Promise(() => {
  console.log("我会立刻被执行");
})

// executor有两个参数:reoslve、reject
const promise = new Promise((resolve, reject) => {
    resolve() // 立刻执行promise.then()
    reject() // 立刻执行promise.catch()
})

下面这几种使用方式都是类似的

const promise = new Promise((resolve, reject) => {})

//写法一
promise.then( res => {
    console.log(res)
}, err => {
    console.log(err)
})

//写法二
promise.then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})

resolve或reject传递参数时,如果参数也是一个promise,那么最终Promise的状态由这个参数promise决定

2、promise的三种状态

1、待定(pending):初始状态,既没有被兑现,也没有被拒绝(当执行executor中的代码时,处于该状态)

2、已兑现(fulfilled):意味着操作成功,执行了resolve时,处于该状态

3、已拒绝(rejected):意味着操作失败,执行了reject时,处于该状态

new Promise((resolve, reject) => {
    console.log('executor')
    //resolve()
    //reject()
}).then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

状态一旦确定,便不可更改,但不意味着后面的其他代码不能被执行

3、resolve的参数

1、参数是普通的值,则直接传递

new Promise((resolve, reject) => {
  resolve("参数")
}).then(res => {
  console.log(res)
})
//输出:参数

2、参数是一个Promise

如果参数是一个promise,那么当前promise的状态会由传入的promise来决定

const inSidePromise = new Promise((resolve, reject) => {
    resolve("我是inSidePromise")
})

new Promise((resolve, reject) => {
  resolve(inSidePromise)
}).then(res => {
  console.log(res)
})

//输出:我是inSidePromise

3、thenable

如果传入一个对象,并且这个对象有实现then方法,那么也会执行then方法,并且由该then方法决定后续状态

new Promise((resolve, reject) => {
    const obj = {
        then: function(resolve, reject) {
            reject("reject message")
        }
    }
    resolve(obj)
}).then(res => {
    console.log('res:', res)
}, err => {
    console.log('err:', err)
})
//本来应该输出obj对象
//结果输出:err: reject message

1.2 promise的对象方法

promise的对象方法是放在Promise的原型上的,我们可以打印一下上面都有什么方法

Promise.prototype
//输出:Object [Promise] {} 

//遍历所有属性描述器
Object.getOwnPropertyDescriptors(Promise.prototype)
//输出:constructor、then、catch、finally等

1、then方法

特点1:同一个promise可以被多次调用then方法

当我们的resolve方法被回调时,所有then方法传入的回调函数都会被调用

const promise = new Promise((resolve, reject) => {
    resolve("linlinlin~")
})
promise.then(res => {
    console.log('res1:', res)
})
promise.then(res => {
    console.log('res2:', res)
})
promise.then(res => {
    console.log('res3:', res)
})
//输出:
//res1: linlinlin~
//res2: linlinlin~
//res3: linlinlin~

特点2:then方法传入的“回调函数”,可以有返回值

如果我们返回一个普通值,那么这个普通值会被作为一个新的promise的resolve值

promise.then(res => {
    return 'abcdefg'
})

//相当于
promise.then(res => {
    return new Promise(resolve => {
        resolve('abcdefg')
    })
})

所以,then方法本身也有返回值,它返回了一个promise

const promise = new Promise((resolve, reject) => {
  resolve('1111111')
})

promise.then(res1 => {
  console.log(res1);
  return '2222222'
}).then(res2 => {
  console.log(res2);
})
//输出:1111111   2222222

补充:

如果返回的是一个promise,里边还是会使用一个Promise包裹return的promise,但是最终状态由里边的promise决定;

如果返回的是对象,并且对象实现了thenable,会执行其中的then方法,并且由该then方法决定后续状态

2、catch方法

利用then的第二个参数捕获错误

const promise = new Promise((resolve, reject) => {
    reject('rejected status')
})

promise.then(undefined, err => {
    console.log("err:", err)
})

即使不使用reject,当executor抛出异常时,也是会调用错误捕获的回调函数的

const promise = new Promise((resolve, reject) => {
    throw new Error("rejected status")
})
promise.then(undefined, err => {
    console.log("err:", err) //触发
})

但是利用then方法的第二个参数捕获异常,会使得代码十分臃肿。所以更推荐使用catch方法捕获异常

const promise = new Promise((resolve, reject) => {
    reject('rejected status')
})

promise.catch(err => {
    console.log("err:", err)
})

思考:下方的链式调用中,catch捕获的是谁的异常

const promise = new Promise((resolve, reject) => {
    reject('rejected status')
})

promise.then(res => {
    return new Promise((resolve, reject) => {
        reject("then rejected status")
    })
}).catch (err => {
    console.log("谁的err", err)
})
//输出:谁的err rejected status
//上面的代码中有两个promise,err默认先捕获了外层的错误

3、finally方法

表示无论Promise对象无论变成fulfilled还是reject状态,最终都会被执行的代码

const promise = new Promise((resolve, reject) => {
    reject("reject")
})

promise.then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
}).finally(() => {
    console.log("finally action")
})

1.3 promise的类方法

前面学习的then、catch、finally方法都属于Promise的实例方法,都是存放在Promise的prototype上。还有直接存放在Promise类上的方法

1、resolve方法

思考:如何将一个对象转换成promise对象,传给其他人调用

//一般方法
function foo() {
    let obj = {
        name: 'xiaoming'
    }
    return new Promise(resolve => {
        resolve(obj)
    })
}
//调用
foo().then(res => {
    console.log(res)
})

使用Promise.resolve()方法也可以

const promise = Promise.resolve({name: 'xiaoming'})
promise.then(res => {
    console.log('res:', res)
})

2、reject方法

reject方法与resolve方法类似

const promise = Promise.reject("rejected message")
//相当于
const promise2 = new Promise((resolve, reject) => {
    reject("rejected message2")
})

3、all方法

promise.all方法,可以将多个Promise包裹在一起形成一个新的Promise

新的promise状态由参数共同决定(所有参数状态为fulfilled,返回一个结果数组;但是只要有一个参数状态为reject,返回reject)

const p1 = new Promise((resolve, reject) => {
    resolve('1111')
})
const p2 = new Promise((resolve, reject) => {
    resolve('2222')
})

Promise.all([p1, p2]).then(res => {
    console.log(res)
})
//输出:[ '1111', '2222' ]
const p1 = new Promise((resolve, reject) => {
    reject('error') //reject
})
const p2 = new Promise((resolve, reject) => {
    resolve('2222')
})

Promise.all([p1, p2]).then(res => {
    console.log(res)
}).catch(err => {
    console.log(err)
})
//输出:error

4、race方法

只要有一个promise状态发生了改变,就停止执行返回结果

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("1111");
  }, 1000); //1s
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("2222");
  }, 2000); //2s
});

Promise.race([p1, p2])
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });
//输出:1111

1.4 promise的实现

1、基本结构

1、Promise接收一个参数(executor函数),并且创建实例时,会直接执行executor函数

class MzPromise {
    constructor(executor) {
        executor()
    }
}

const promise = new MzPromise(() => {
    console.log("传进来的函数被执行了")
})
//输出:传进来的函数被执行了

2、加入resolve,reject函数

executor函数,接收两个函数作为参数

class MzPromise {
    constructor(executor) {
        const resolve = () => {
            console.log("resolve被调用了")
        }
        const reject = () => {
            console.log("reject被调用了")
        }
        executor(resolve, reject)
    }
}

const promise = new MzPromise((resolve, reject) => {
    resolve()
    reject()
})

//输出:resolve被调用了、reject被调用了

问题:resolve、reject不能同时被调用。要么状态是fulfilled、要么是rejected

3、加入三种状态

const PROMISE_STATUS_PENDDING = "pending";
const PROMISE_STATUS_FULFILED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
class MzPromise {
  constructor(executor) {
    //默认状态是pending
    this.status = PROMISE_STATUS_PENDDING;

    const resolve = () => {
      if (this.status === PROMISE_STATUS_PENDDING) {
        //改变状态
        this.status = PROMISE_STATUS_FULFILED;
        console.log("resolve被调用了");
      }
    };
    const reject = () => {
      if (this.status === PROMISE_STATUS_PENDDING) {
        //改变状态
        this.status = PROMISE_STATUS_REJECTED;
        console.log("reject被调用了");
      }
    };
    executor(resolve, reject);
  }
}
//测试
const promise = new MzPromise((resolve, reject) => {
    resolve()
    reject()
})
//输出:resolve被调用了

率先执行了resolve函数,状态变为满足状态,之后的reject便不会执行了

4、在resolve、reject中传入参数

class MzPromise {
    constructor(executor) {
        //默认状态是pending
        this.status = PROMISE_STATUS_PEDDING
        this.value = undefined
        this.reason = undefined

        const resolve = (value) => {
            if(this.status === PROMISE_STATUS_PEDDING) {
                this.status = PROMISE_STATUS_FULFILED
                this.value = value //保存传递来的value
                console.log("resolve被调用了")
            }
        }
        const reject = (reason) => {
            if(this.status === PROMISE_STATUS_PEDDING) {
                this.status = PROMISE_STATUS_REJECTED
                this.reason = reason //保存传递来的value
                console.log("reject被调用了")
            }
        }
        executor(resolve, reject)
    }
}

2、对象方法then的实现

我们知道,当Promise执行resolve()时,会调用then方法

new Promies(resolve => {
    resolve() //执行
}).then(res => {

}, err => {

})

then方法的回调在哪里调用呢?

class MzPromise {
    constructor(executor) {
        const resolve = (value) => {
            if(this.status === PROMISE_STATUS_PEDDING) {
                this.status = PROMISE_STATUS_FULFILED
                this.value = value //保存传递来的value
                console.log("resolve被调用了")
                then传进来的回调函数1
            }
        }
        const reject = (reason) => {
            if(this.status === PROMISE_STATUS_PEDDING) {
                this.status = PROMISE_STATUS_REJECTED
                this.reason = reason //保存传递来的value
                console.log("reject被调用了")
                then传进来的回调函数2
            }
        }
        executor(resolve, reject)
    }
}

实现then方法,并在相应的resolve或者reject中调用

class MzPromise {
    constructor(executor) {
        const resolve = (value) => {
            if(this.status === PROMISE_STATUS_PEDDING) {
                this.status = PROMISE_STATUS_FULFILED
                this.value = value
                console.log("resolve被调用了")
                //then传进来的回调函数1
                this.onFulfilled() //调用
            }
        }
        executor(resolve, reject)
    }
    //then方法-MzPromise的对象方法
    then(onFulfilled, onReject) {
        this.onFulfilled = onFulfilled //保存传来的onFulfilled函数
        this.onReject = onReject //保存传来的onReject函数
    }
}

但是执行时,报错了。主要是this.onFulfilled()的执行时机有问题。

当我们创建实例时,执行了resolve()函数,此时会调用MzPromise中的resolve的方法,执行到this.onFulfilled()行时,找不到该方法,直接就报错了。

解决办法就是将MzPromise中的resolve的方法延迟执行,直到then方法已被执行

//使用setTimeout延迟
class MzPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PEDDING

        const resolve = (value) => {
            if(this.status === PROMISE_STATUS_PEDDING) {
                this.status = PROMISE_STATUS_FULFILED
                setTimeout(() => {
                    this.value = value //保存传递来的value
                    this.onFulfilled(this.value)
                }, 0)
            }
        }
        const reject = (reason) => {
            if(this.status === PROMISE_STATUS_PEDDING) {
                this.status = PROMISE_STATUS_REJECTED
                setTimeout(() => {
                    this.reason = reason //保存传递来的value
                    this.onReject(this.reason)
                }, 0)
            }
        }
        executor(resolve, reject)
    }

    // then 方法
    then(onFulfilled, onReject) {
        this.onFulfilled = onFulfilled
        this.onReject = onReject
    }
}

const promise = new MzPromise((resolve, reject) => {
    resolve("我是resolve")
    reject("我是reject")
})


promise.then(res => {
    console.log("res:", res)
}, err => {
    console.log( "err:", err);
})
//输出:res: 我是resolve

上方的代码,基本实现了then方法。但是Promise是微任务队列的任务,而setTimeout属于宏任务,使用setTimeout显然不合适

应该利用一个能将代码添加进微任务,又能延迟执行的方法——queueMicrotask

class MzPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PEDDING

        const resolve = (value) => {
            if(this.status === PROMISE_STATUS_PEDDING) {
                this.status = PROMISE_STATUS_FULFILED
                queueMicrotask(() => {
                    this.value = value //保存传递来的value
                    this.onFulfilled(this.value)
                })
            }
        }
        executor(resolve, reject)
    }

}

3、then方法的优化

问题1:上边的then方法还存在着许多缺陷:比如不能多次调用

解决办法:分别将then方法中resolve、reject回调放进数组中,调用时直接遍历出数组中的函数并进行调用

class MzPromise {
    constructor(executor) {
        this.onFulfilledFns = []
        this.onRejectFns = []

        const resolve = (value) => {
            if(this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_FULFILED
                    queueMicrotask(() => {
                        console.log('resolve被调用了');
                        this.value = value
                        this.onFulfilledFns.forEach(item => {
                            item(this.value)
                        })
                    })
            }
        }
        executor(resolve, reject)
    }
    then(onFulfilled, onReject) {
        // 将成功和失败的回调放进数组中
        this.onFulfilledFns.push(onFulfilled)
        this.onRejectFns.push(onReject)
    }
}

这样一来就可以多次调用了

promise.then(res => {
    console.log('res:', res);
}, err => {
    console.log('err:', err);
})

promise.then(res => {
    console.log('res1:', res);
}, err => {
    console.log('err2:', err);
})

问题2:在确定Promise状态后,再次调用then将不会执行

setTimeout(() => {
    promise.then(res => {
        console.log('res3:', res);
    }, err => {
        console.log('err3:', err);
    })
}, 1000)
//输出:无输出

解决

class MzPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledFns = []
        this.onRejectFns = []

        const resolve = (value) => {
            if(this.status === PROMISE_STATUS_PENDING) {
                // 添加微任务
                    queueMicrotask(() => {
                        if(this.status !== PROMISE_STATUS_PENDING) return 
                        this.status = PROMISE_STATUS_FULFILLED
                        console.log('resolve被调用了');
                        this.value = value
                        this.onFulfilledFns.forEach(item => {
                            item(this.value)
                        })
                    })
            }
        }
        executor(resolve, reject)
    }
    then(onFulfilled, onReject) {
        if(this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
            onFulfilled(this.value)
        }
        if(this.status === PROMISE_STATUS_REJECTED && onReject) {
            onReject(this.reason)
        }
        // 将成功和失败的回调放进数组中
        this.onFulfilledFns.push(onFulfilled)
        this.onRejectFns.push(onReject)
    }
}

下一步优化要实现then的链式调用,暂略

目前阶段完整代码

const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'


class MzPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledFns = []
        this.onRejectFns = []

        const resolve = (value) => {
            if(this.status === PROMISE_STATUS_PENDING) {
                // 添加微任务
                    queueMicrotask(() => {
                        if(this.status !== PROMISE_STATUS_PENDING) return 
                        this.status = PROMISE_STATUS_FULFILLED
                        console.log('resolve被调用了');
                        this.value = value
                        this.onFulfilledFns.forEach(item => {
                            item(this.value)
                        })
                    })
            }
        }
        const reject = (reason) => {
            if(this.status === PROMISE_STATUS_PENDING) {
                // 添加微任务
                    queueMicrotask(() => {
                        if(this.status !== PROMISE_STATUS_PENDING) return 
                        this.status = PROMISE_STATUS_REJECTED
                        console.log("reject被调用了");
                        this.reason = reason
                        this.onRejectFns.forEach(item => {
                            item(this.reason)
                        })
                    })
            }
        }
        executor(resolve, reject)
    }
    then(onFulfilled, onReject) {
        if(this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
            onFulfilled(this.value)
        }
        if(this.status === PROMISE_STATUS_REJECTED && onReject) {
            onReject(this.reason)
        }
        // 将成功和失败的回调放进数组中
        this.onFulfilledFns.push(onFulfilled)
        this.onRejectFns.push(onReject)
    }
}

const promise = new MzPromise((resolve, reject) => {
    console.log("executor");
    reject('err')
    // resolve('res')

})

promise.then(res => {
    console.log('res:', res);
}, err => {
    console.log('err:', err);
})

4、类方法的实现

1、实现resolve方法

class MzPromise {
    static resolve(value) {
        return new MzPromise((resolve) => resolve(value))
    }
}

//创建实例
MzPromise.resolve("hello woeld") //把该字符串转成了promise
.then(res => {
    concole.log('res:', res)
})
//输出:res:hello world

2、实现reject方法

class MzPromise {
    static reject(reason) {
        return new MzPromise((resolve, reject) => reject(reason))
    }
}

//创建实例
MzPromise.reject("error").then(undefined, err => {
    console.log('err:', err);
})
//输出:err: error

3、实现all方法

问题关键:什么时候执行resolve,什么时候要执行reject

class MzPromise {
    static all(promises) {
        return new MzPromise((resolve, reject) => {
            const values = []
            promises.forEach(promise => {
                promise.then(res => {
                    values.push(res)
                    if(values.length === promises.length) {
                        resolve(values)
                    }
                }, err => {
                    reject(err)
                })
            })
        })
    }
}
//测试代码
const p1 = new Promise((resolve, reject) => {
    resolve('10')
})
const p2 = new Promise((resolve, reject) => {
    resolve("20")
})
const p3 = new Promise((resolve, reject) => {
    resolve('30')
})

MzPromise.all([p1, p2, p3]).then(res => {
    console.log('res:', res);
}, err => {
    console.log('err:', err)
})
//输出:res: [ '10', '20', '30' ]

4、实现race方法

static race(promises) {
    return new MzPromise((resolve, reject) => {
        promises.forEach(promise => {
            promise.then(res => {
                //只要一有结果,立刻resolve
                resolve(res)
            }, err => {
                reject(err)
            })
        })
    })
}
//测试代码
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('10')
    }, 1000)
})
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('20')
    }, 2000)
})
const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('30')
    }, 3000)
})

MzPromise.race([p1, p2, p3]).then(res => {
    console.log('res:', res);
}, err => {
    console.log('err:', err)
})
//输出:res:10
评论
  • 支持 Markdown 格式
  • 评论需要登录
  • 邮箱会回复提醒(也许会在垃圾箱内)
0 /400 字