Promise原理解析
前言
最近通过学习javascrip的异步处理了解到了Promise对象,并学习了对Promise原理的分析,以此记录!
Promise的原理解析
通过封装库的思想手动构造一个Promise我学习到从以下几个方面去考虑
Promise 中三种状态的实现
Promise 中then的实现方法
Promsie 中
一、Promise 的三种状态
Promise中默认 pending 状态,调取 resolve方法 成功返回 fulfilled 状态,调取 reject方法 成功返回 rejected 状态
// Promise类 let p = new Promise((resolve, reject) => { // resolve('success') // reject('err') }) console.log(p) // 返回Promise对象 其中[[PromiseState]]: "pending" [[PromiseResult]]: undefined 复制代码
根据返回对象我们可以自己构造一个方法实现简单的展现Promise的状态
class MPromise { constructor(handle) { // 默认状态 this['[[PromiseState]]'] = 'pending' this['[[PromiseResult]]'] = undefined handle(this.#resolve.bind(this), this.#reject.bind(this)) } // 成功时调取函数 #resolve(val) { this['[[PromiseState]]'] = 'fulfilled' this['[[PromiseResult]]'] = val } // 失败时调取函数 #reject(err) { this['[[PromiseState]]'] = 'rejected' this['[[PromiseResult]]'] = err } } let p = new MPromise((resolve, reject) => { // resolve('success') reject('err') }) console.log(p) // 复制代码
这样就实现了一个简易的呈现状态的Promise
二、then的问题
多个 then 的实现
then 的返回值
微任务宏任务
链式操作
实现then方法
Promise中这样调取
let p = new Promise((resolve, reject) => { resolve('success') reject('err') }) p.then((res) => { console.log(res) // success },err=>{ console.log(err) // err }) 复制代码
手动构造一个 then 的方法 注意:then 方法不能立刻执行,当调取resolve或者reject之后才能执行 then 否则直接执行 then 延迟执行resolve或reject时会抛出undefined,代码如下:
class MPromise { constructor(handle) { // 默认状态 this['[[PromiseState]]'] = 'pending' this['[[PromiseResult]]'] = undefined handle(this.#resolve.bind(this), this.#reject.bind(this)) } // 成功时调取函数 #resolve(val) { this['[[PromiseState]]'] = 'fulfilled' this['[[PromiseResult]]'] = val } // 失败时调取函数 #reject(err) { this['[[PromiseState]]'] = 'rejected' this['[[PromiseResult]]'] = err } then(onResolved, onRejected) { if ((this['[[PromiseState]]'] = 'fulfilled')) { onResolved && onResolved(this['[[PromiseResult]]']) } else { onRejected && onRejected(this['[[PromiseResult]]']) } } } let p = new MPromise((resolve, reject) => { setTimeout(() => { resolve('success') // reject('err') }) }) p.then((res) => { console.log(res) } 复制代码
那么解决这一问题的方法就是在调取resolve或者reject后执行,代码如下:
class MPromise { constructor(handle) { // 默认状态 this['[[PromiseState]]'] = 'pending' this['[[PromiseResult]]'] = undefined this.resolveFn = undefined this.rejectFn = undefined handle(this.#resolve.bind(this), this.#reject.bind(this)) } // 成功时调取函数 #resolve(val) { this['[[PromiseState]]'] = 'fulfilled' this['[[PromiseResult]]'] = val this.resolveFn(val) } // 失败时调取函数 #reject(err) { this['[[PromiseState]]'] = 'rejected' this['[[PromiseResult]]'] = err this.resolveFn(val) } then(onResolved, onRejected) { // 将状态保存起来 this.resolveFn = onResolved this.rejectFn = onRejected } } 复制代码
这样我们延迟调用resolve或reject就不会出现undefined现象
多个then的问题
上面的方法虽然解决延时调用的问题,但如果我们调用多个 then 方法会出现覆盖问题,总是获取到最后一次调取的值,所以多个 then 的解决方法就是通过数组存储,依次执行
class MPromise { constructor(handle) { // 默认状态 this['[[PromiseState]]'] = 'pending' this['[[PromiseResult]]'] = undefined this.resolveQueue = [] this.rejectQueue = [] handle(this.#resolve.bind(this), this.#reject.bind(this)) } // 成功时调取函数 #resolve(val) { this['[[PromiseState]]'] = 'fulfilled' this['[[PromiseResult]]'] = val const run = () => { let cb while ((cb = this.resolveQueue.shift())) { cb && cb(val) } } run() } // 失败时调取函数 #reject(err) { this['[[PromiseState]]'] = 'rejected' this['[[PromiseResult]]'] = err const run = () => { let cb while ((cb = this.resolveQueue.shift())) { cb && cb(err) } } run() } then(onResolved, onRejected) { // 将状态保存起来 this.resolveQueue.push(onResolved) this.rejectQueue.push(onRejected) } } let p = new MPromise((resolve, reject) => { setTimeout(() => { resolve('success') // reject('err') }) }) p.then((res) => { console.log(res) }) p.then((res) => { console.log('22') }) 复制代码
但是这样还有一个执行顺序问题,如果同步执行的话,最后执行 then 方法,导致同步执行没有结果,解决这一问题的方法就是先执行 then 方法,所以让resolve和reject变成异步方法,代码如下:
// 将上面代码中run方法修改为异步方法即可 setTimeout(run) 复制代码
这样不论是同步还是异步,调用 then 都会获取结果
微任务与宏任务
上面我们解决了同步和异步时都会调用 then 方法的问题,然而还有一个问题需要解决,先看问题:
// 输出顺序问题 // Promise setTimeout(() => { console.log('11') }) console.log('22') let p = new Promise((resolve, reject) => { resolve('33') }) p.then((res) => { console.log(res) }) p.then((res) => { console.log('44') }) console.log('55') // 输出顺序为 22-55-33-44-11 复制代码
// 自定义方法 // MPromise为上述方法 setTimeout(() => { console.log('11') }) console.log('22') let p = new MPromise((resolve, reject) => { resolve('33') }) p.then((res) => { console.log(res) }) p.then((res) => { console.log('44') }) console.log('55') // 输出顺序为 22-55-11-33-44 复制代码
微任务:一个需要异步执行的函数,执行时机是在主函数执行结束自后、当前宏任务结束之前
宏任务:宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
then 方法是一个微任务
执行时 首先是同步代码执行,所以先输出 22 55 之后执行异步代码,因为一个宏任务执行后要先执行它所包含的微任务,然后在去执行下一个宏任务,setTimeout属于下一个宏任务,所以先输出 33 44
那如何修改自己构造的方法呢?
MutationObserver 方法
developer.mozilla.org/zh-CN/docs/…
利用这个方法我们可以通过监听属性变化,进行函数调用,变成异步微任务,代码如下:
class MPromise { constructor(handle) { // 默认状态 this['[[PromiseState]]'] = 'pending' this['[[PromiseResult]]'] = undefined this.resolveQueue = [] this.rejectQueue = [] handle(this.#resolve.bind(this), this.#reject.bind(this)) } // 成功时调取函数 #resolve(val) { this['[[PromiseState]]'] = 'fulfilled' this['[[PromiseResult]]'] = val const run = () => { let cb while ((cb = this.resolveQueue.shift())) { cb && cb(val) } } const observe = new MutationObserver(run) observe.observe(document.body, { attributes: true }) document.body.setAttribute('name', 'a') } // 失败时调取函数 #reject(err) { this['[[PromiseState]]'] = 'rejected' this['[[PromiseResult]]'] = err const run = () => { let cb while ((cb = this.resolveQueue.shift())) { cb && cb(err) } } const observe = new MutationObserver(run) observe.observe(document.body, { attributes: true }) document.body.setAttribute('name', 'a') } then(onResolved, onRejected) { // 将状态保存起来 this.resolveQueue.push(onResolved) this.rejectQueue.push(onRejected) } } setTimeout(() => { console.log('11') }) console.log('22') let p = new MPromise((resolve, reject) => { resolve('33') }) p.then((res) => { console.log(res) }) p.then((res) => { console.log('44') }) console.log('55') // 通过MutationObserver模拟一个微任务,所以输出顺序就是 22-55-33-44-11 复制代码
链式操作
先来看一下 Promise 中 then 的链式操作
let p = new Promise((resolve, reject) => { resolve('success') }) p.then((res) => { console.log(res) return new Promise((resolve, reject) => { resolve('success') }) }).then((res) => { console.log(res) }) // 打印两个success 复制代码
也就是说在 Promise 的 then 中可以通过 return 返回并通过下一个 then 调取,所以我的思路就是先让自己构造的 then 方法可以返回一个新包装的 Promise 对象 代码如下:
class MPromise { constructor(handle) { // 默认状态 this['[[PromiseState]]'] = 'pending' this['[[PromiseResult]]'] = undefined this.resolveQueue = [] this.rejectQueue = [] handle(this.#resolve.bind(this), this.#reject.bind(this)) } // 成功时调取函数 #resolve(val) { this['[[PromiseState]]'] = 'fulfilled' this['[[PromiseResult]]'] = val const run = () => { let cb while ((cb = this.resolveQueue.shift())) { cb && cb(val) } } const observe = new MutationObserver(run) observe.observe(document.body, { attributes: true }) document.body.setAttribute('name', 'a') } // 失败时调取函数 #reject(err) { this['[[PromiseState]]'] = 'rejected' this['[[PromiseResult]]'] = err const run = () => { let cb while ((cb = this.resolveQueue.shift())) { cb && cb(err) } } const observe = new MutationObserver(run) observe.observe(document.body, { attributes: true }) document.body.setAttribute('name', 'a') } then(onResolved, onRejected) { return new MPromise((resolve, reject) => { // 拿到结果 且不可立即执行 let resolveFn = function (val) { let result = onResolved && onResolved(val) // 返还MPromise对象 if (result instanceof MPromise) { result.then(resolve) } else { resolve(result) } } this.resolveQueue.push(resolveFn) let rejectFn = function (err) { onRejected && onRejected(err) reject(err) } this.rejectQueue.push(rejectFn) }) // 将状态保存起来 } } let p = new MPromise((resolve, reject) => { resolve('33') }) p.then((res) => { console.log(res) return new MPromise((resolve) => { resolve('11') }) }).then((res) => { console.log(res) }) // 结果打印 33,11 复制代码
三、周边方法
原型方法
catch
catch主要是通过 then 方法实现的 通过给 then 中的onReject传入值即可
catch(fn) { return this.then(undefined, fn) } let p = new MPromise((resolve, reject) => { reject('err') }) p.then((res) => { console.log(res) }).catch((err) => { console.log(err) // err }) 复制代码
finally
finally(cb) { this.then(cb, cb) } 复制代码
finally方法放在then后面执行就好
静态方法
resolve/reject
也是通过返回一个 MPromise 对象来构造的方法
static resolve(val) { return new MPromise((resolve) => { resolve(val) }) } static reject(err) { return new MPromise((resolve, reject) => { reject(err) }) } let p = MPromise.reject('err') console.log(p) // 返回一个 MPromise 对象 复制代码
race
race 的参数是一个包含Promise对象的数组
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 2000) }) let p2 = new Promise((resolve, reject) => { setTimeout(() => { reject('err') }, 1000) }) Promise.race([p1, p2]).then( (res) => { console.log(res) }, (err) => { console.log(err) } ) // 结果打印 err 复制代码
自己实现的思路就是定义一个静态函数返回Promise对象
static race(lists) { return new MPromise((resolve, reject) => { lists.forEach((item) => { item.then( (res) => { resolve(res) }, (err) => { reject(err) } ) }) }) } 复制代码
all
只返回resolve中的值
static all(lists) { let resArr = [] let num = 0 return new MPromise((resolve) => { lists.forEach((item) => { item.then((res) => { num++ resArr.push(res) if (resArr.length === lists.length) { resolve(resArr) } }) }) }) } 复制代码
allSettled
allSettled 会把 Promise 成功或者失败的值都获取到
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 1000) }) let p2 = new Promise((resolve, reject) => { setTimeout(() => { reject('err') }, 2000) }) Promise.allSettled([p1, p2]).then((res) => { console.log(res) }) 复制代码
手动实现 allSettled 方法
static allSettled(lists) { let resArr = new Array(lists.length) let num = 0 return new MPromise((resolve,reject) => { lists.forEach((item, key) => { let obj = {} item.then( (res) => { obj['statue'] = 'fulfilled' obj['val'] = res resArr[key] = obj num++ if (num >= lists.length) { resolve(resArr) } }, (err) => { obj['statue'] = 'rejected' obj['val'] = err resArr[key] = obj num++ if (num >= lists.length) { reject(resArr) } } ) }) }) } 复制代码
总结
个人觉得对 Promise 的理解可能还是没有很深入的理解,毕竟刚刚入门 希望看到这篇文章的人有什么想法能够和笔者交流,有问题的地方及时指出,共同进步!
作者:爱泡澡的小萝卜
链接:https://juejin.cn/post/7022178384520151070