Open jiefancis opened 2 years ago
promise是我们日常工作中几乎都要用到的一个api,它的出现帮助我们解决了异步问题,至于回调地狱,如果层级多了,promise还是会产生回调地狱,不过我们可以借助async/await一起使用来解决回调地狱的问题。使用了这么久的promise,对于promise如何实现的也没有一窥全貌,所以这次跟大家一起分享它里面的实现。
在讲解promise原理之前,我们先来了解eventLoop与宏微任务。
js自带api
需要判断环境
事件循环机制的执行过程简单提一点:js代码从上至下执行,遇到宏(微)任务,将宏(微)任务存在宏(微)任务事件队列中等待,等待js代码执行完后,再到微任务事件队列中查找是否还有任务在等待执行,如果微任务事件队列没有任务,同理,到宏任务事件队列执行。直到宏任务微任务执行完毕。 本次分享的重点不在这,有兴趣想了解更多请查阅
Promise.resolve().then(() => { console.log(0); return Promise.resolve(4); }).then((res) => { console.log(res) })
Promise.resolve().then(() => { console.log(1); }).then(() => { console.log(2); }).then(() => { console.log(3); }).then(() => { console.log(5); }).then(() =>{ console.log(6); })
输出结果是:0 1 2 3 4 5 6
输出结果为什么跟我们预想的 0 1 4 2 3 5 6不一样呢? # promiseA+规范 new Promise().then(),如果then中返回一个promise(简称p0),(V8源码中)则会执行()=>p0.then(完成p0的状态变化),同时将() => p0.then(完成p0的状态变化) 添加到异步队列中。 # 题目的运行过程 以输出数字为p的标记 1. p0(() => { console.log(0); return Promise.resolve(4)})进入异步队列中,此时异步队列[p0]; 2. p1进入异步队列中,此时异步队列[p0,p1]。 3. 执行p0,输出0。return Promise.resolve(4)则是转换成 () => p4.then(完成p0的状态变化)添加到异步队列中,此时异步队列[p1, () => p4.then(完成p0的状态变化)] 4. 输出1,由于p1状态变为fulfilled,p2进入异步队列,[() => p4.then(完成p0的状态变化), p2] 5. 执行p4.then(完成p0的状态变化),(完成p0的状态变化即pres = (res) => res), pres进入异步队列,此时异步队列为[p2, pres] 6. 输出2,p2状态变为fulfilled,p3进入异步队列,此时异步队列为[pres, p3] 7. 执行pres,p0状态变为fulfilled,后续打印res的then回调进入异步队列中,此时[p3, () => console.log(res)] 8. 输出3,p5进入异步队列中 9. 输出4 10. 输出5,p6进入异步队列中 11. 输出6 # 实现一个promise ## 使用方式 1. 常规
new Promise((resolve, reject) => { resolve(1) }) .then(res => console.log(res)) .catch(err => console.log(err))
2. 抛出异常
new Promise((resolve, reject) => { throw new Error('test') }) .then(res => console.log(res)) .catch(err => console.log(err))
3. 异步resolve
new Promise((resolve, reject) => { setTimeout(() => resolve(1)) }) .then(res => console.log(res)) .catch(err => console.log(err))
4. 链式调用
new Promise((resolve, reject) => { resolve(1) }) .then(res => console.log(1-then, res)) .catch(err => console.log(1-catch, err)) .then(res => console.log(2-then,res)) .catch(err => console.log(2-catch,err)) .then(res => console.log(3-then,res)) .catch(err => console.log(3-catch,err))
1-then
1-catch
2-then
2-catch
3-then
3-catch
5. then参数可选
new Promise((resolve, reject) => { resolve(1) }) .then() .then() .then(res => console.log(3-then,res))
## 实现一个简单的Promise new Promise((resolve, reject) => { resolve() }).then(res => { console.log('then', res) }).catch(err => { console.log('catch', err) })
new Promise((resolve, reject) => {})
// 作为MyPromise的形参,这形参是一个函数,又有自己的形参,实参在函数执行的时候传入 function executor(resolve, reject) {} const FULFILLED = 'fulfilled' const REJECTED = 'rejected' const PENDING = 'pending' function MyPromise(executor) { // 上下文共享状态 const ctx = this.ctx = { value: '', reason: '', status: 'pending' }
// resolve实现状态改变及保存数据到实例属性中 const resolve = (value) => { // 状态变化只能从pending转变为fulfilled或者rejected,且更改后无法再次更改 if(ctx.status === PENDING) { ctx.value = value ctx.status = FULFILLED } } // reject实现状态改变及保存数据到实例属性中 const reject = (reason) => { // 状态变化只能从pending转变为fulfilled或者rejected,且更改后无法再次更改 if(ctx.status === PENDING) { ctx.reason = reason ctx.status = REJECTED } } executor(resolve, reject)
} MyPromise.prototype.then = function(res){ if(this.ctx.status === PENDING) { console.log('then', res) } }
MyPromise.prototype.catch = function(err){ if(this.ctx.status === PENDING) { console.log('catch', err) } }
## 异常捕获(try-catch) 关于异常捕获,有很多方式,比较被我们大家熟知的是window.onerror,try-catch,这里我们用try-catch。 1. new Promise()抛出异常
new MyPromise((resolve, reject) => { throw new Error() })
function MyPromise(executor) { ... try { executor(resolve, reject) } catch(err) { reject(err) }
}
2. then中onFulfilled及onRejected执行抛出异常
MyPromise.prototype.then = function(onFulfilled, onRejected) { if(this.ctx.status === FULFILLED) { try{ onFulfilled(this.ctx.value) } catch(err) { console.log('catch error', err) } } } MyPromise.prototype.then = function(onFulfilled, onRejected) { // 实现链式调用,同时确保每次返回的都是一个新的promise。 const _promise = new MyPromise((resolve, reject) => { if(this.ctx.status === PENDING) { try{ const result = onFulfilled(this.ctx.value) resolvePromise(promise2, x, resolve, reject); } catch(err) { reject(err) }
} else if(this.ctx.status === FULFILLED) { onRejected(this.ctx.reason) } else if(this.ctx.status === REJECTED) { } }) return _promise
P.then().catch()
## 异步resolve 我们在初始化Promise实例的时候,会遇到这种情况,我们不会立即resolve,而是等待一段时间的延迟或者等待接口请求完成后才resolve,而我们前面的实现都是同步的方式。 - resolve职责 在改动resolve实现逻辑之前,需要先明确resolve的职责: 判断当前状态为pending,才去更改实例的状态为fulfilled(rejected); - then的职责: 状态为fulfilled(rejected)才执行onFulfilled(onRejected)。 实例化时没有resolve()走到then函数中执行时,当前实例的状态是pending,这时then是不会执行onFulfilled函数的。 - 框架的处理方式 对于这种异步执行的场景,前端框架里面常用的方式是增加事件队列+异步处理来实现延迟,这些场景大多是同步的方式下加入宏微任务队列来达到延迟的目的。 - 上下文共享状态(实例属性) 但promise这里已经是异步,如果再加入异步,我们如何确保这两个异步的执行顺序?至于是否可以加入发布订阅模式来处理,这里不深入。回过头来看说明then在resolve前面执行,我们可以先将onFulfilled(onRejected)保存在this.ctx属性中到resolve中执行,在其前面加个判断条件执行。
const p = new MyPromise((resolve, reject) => { setTimeout(() => { resolve(1) }, 1000) }) p.then(res => {
})
==========resolve改动=============== const onFulfilledQueue = []
const resolve = (value) => { if(ctx.status === PENGDING) { ctx.status = FULFILLED ctx.value = value if(onFulfilledQueue.length) { onFulfilledQueue.forEach(fn => fn()) } } }
==========then改动=============== MyPromise.prototype.then = function(onFulfilled, onRejected) { const _promise = new MyPromise((resolve, reject) => { if(this.ctx.status === PENDING) { onFulfilled && this.ctx.onFulfilledQueue.push(onFulfilled) onRejected && this.ctx.onRejectedQueue.push(onRejected) } else if(this.ctx.status === FULFILLED) { const res = onFulfilled(this.ctx.value) resolvePromise(x, resolve, reject) } else if(this.ctx.status === REJECTED) { onRejected(this.ctx.reason) } }) return _promise }
function resolvePromise(res, resolve, reject) { if(res instanceof MyPromise || (res?.then && type res.then === 'function')) { res.then(resolve) } else { resolve(res) } }
## then链式调用 1. promiseA+规范中规定then返回一个新的promise实例 - 关于链式调用,在js里面,我们的做法是在所有的方法中返回当前实例。但是在promise这里行不通;是因为返回当前promise实例,当前promise实例状态从pending->fulfilled,或者pending->rejected后无法再改变,后面将会一直走then的逻辑。 promise本身也不支持onFulfilled(onRejected)返回当前实例,返回则是会报错
var p = new Promise((resolve, reject) => {resolve(1)}).then(res => {console.log(res); return p})
// 报错信息 Uncaught (in promise) TypeError: Chaining cycle detected for promise #
2. onFulfilled(onRejected)返回值 - 返回一个promise实例 - 返回一个类promise对象(拥有then属性) - 返回一个基本数据 - 抛出一个异常
// 例子 new Promise((resolve, reject) => { resolve(1) }).then(res => console.log('then', res)).catch(err => {console.log('catch', err); return err}).then(res => console.log('then2', res)).catch(err => {console.log('catch2', err)})
// then改版 MyPromise.prototype.then = function(onFulfilled, onRejected){ const _promise = new Promise((resolve, reject) => { const { status, value, onFulfilledQueue, onRejectedQueue } = this.ctx;
if (status === FULFILLED) { try{ const x = onFulfilled(value) resolvePromise(x, resolve, reject) } catch (err) { reject(err) } } else if(status === REJECTED) { try{ const x = onRejected(value) resolvePromise(x, resolve, reject) } catch (err) { reject(err) } } else if(status === PENDING) { onFulfilled && onFulfilledQueue.push(onFulfilled) onRejected && onRejectedQueue.push(onRejected) } })
## then参数可选
// 例子 new Promise((resolve, reject) => { resolve(1) }).then().then().then().then().then(res => console.log('then5', res))
// catch new Promise((resolve, reject) => { reject(1) }).then(res => console.log('then', res)).catch().then(res => {console.log('then2', res); return res}).catch(err => {console.log('catch2', err)})
// then改动 MyPromise.prototype.then = function(onFulfilled, onRejected){ onFulfilled = onFulfilled && typeof onFulfilled === 'function' ? onFulfilled : (value) => value; onRejected = onRejected && typeof onRejected === 'function' ? onRejected : (reason) => { throw reason };
const _promise = new Promise((resolve, reject) => { if (status === FULFILLED) { try{ const x = onFulfilled(value) resolvePromise(x, resolve, reject) } catch (err) { reject(err) } } else if(status === REJECTED) { try{ const x = onRejected(value) resolvePromise(x, resolve, reject) } catch (err) { reject(err) } } else if(status === PENDING) { onFulfilled && onFulfilledQueue.push(onFulfilled) onRejected && onRejectedQueue.push(onRejected) } })
## Promise.resolve()
MyPromise.prototype.resove = function(params) { const _promise = new MyPromise((resolve, reject) => { if(params instanceof MyPromise || typeof params?.then === 'function') { params.then(resolve) } else { resolve(params) } }) return _promise }
# refrence [从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节](https://juejin.cn/post/6945319439772434469#heading-15) [V8源码补充篇](https://juejin.cn/post/6953452438300917790#heading-1) [Promise V8 源码分析(一)](https://zhuanlan.zhihu.com/p/264944183) [chrome v8 promise源码](https://chromium.googlesource.com/v8/v8.git/+/refs/heads/9.0-lkgr/src/builtins/promise-then.tq) [promiseA+规范](https://promisesaplus.com/#notes)
前言
promise是我们日常工作中几乎都要用到的一个api,它的出现帮助我们解决了异步问题,至于回调地狱,如果层级多了,promise还是会产生回调地狱,不过我们可以借助async/await一起使用来解决回调地狱的问题。使用了这么久的promise,对于promise如何实现的也没有一窥全貌,所以这次跟大家一起分享它里面的实现。
EventLoop执行机制
在讲解promise原理之前,我们先来了解eventLoop与宏微任务。
微任务
js自带api
需要判断环境
宏任务
事件循环机制的执行过程简单提一点:js代码从上至下执行,遇到宏(微)任务,将宏(微)任务存在宏(微)任务事件队列中等待,等待js代码执行完后,再到微任务事件队列中查找是否还有任务在等待执行,如果微任务事件队列没有任务,同理,到宏任务事件队列执行。直到宏任务微任务执行完毕。 本次分享的重点不在这,有兴趣想了解更多请查阅
从一个问题来看promise
Promise.resolve().then(() => { console.log(1); }).then(() => { console.log(2); }).then(() => { console.log(3); }).then(() => { console.log(5); }).then(() =>{ console.log(6); })
输出结果是:0 1 2 3 4 5 6
new Promise((resolve, reject) => { resolve(1) }) .then(res => console.log(res)) .catch(err => console.log(err))
new Promise((resolve, reject) => { throw new Error('test') }) .then(res => console.log(res)) .catch(err => console.log(err))
new Promise((resolve, reject) => { setTimeout(() => resolve(1)) }) .then(res => console.log(res)) .catch(err => console.log(err))
new Promise((resolve, reject) => { resolve(1) }) .then(res => console.log(
1-then
, res)) .catch(err => console.log(1-catch
, err)) .then(res => console.log(2-then
,res)) .catch(err => console.log(2-catch
,err)) .then(res => console.log(3-then
,res)) .catch(err => console.log(3-catch
,err))new Promise((resolve, reject) => { resolve(1) }) .then() .then() .then(res => console.log(
3-then
,res))new Promise((resolve, reject) => {})
// 作为MyPromise的形参,这形参是一个函数,又有自己的形参,实参在函数执行的时候传入 function executor(resolve, reject) {} const FULFILLED = 'fulfilled' const REJECTED = 'rejected' const PENDING = 'pending' function MyPromise(executor) { // 上下文共享状态 const ctx = this.ctx = { value: '', reason: '', status: 'pending' }
} MyPromise.prototype.then = function(res){ if(this.ctx.status === PENDING) { console.log('then', res) } }
MyPromise.prototype.catch = function(err){ if(this.ctx.status === PENDING) { console.log('catch', err) } }
new MyPromise((resolve, reject) => { throw new Error() })
function MyPromise(executor) { ... try { executor(resolve, reject) } catch(err) { reject(err) }
}
MyPromise.prototype.then = function(onFulfilled, onRejected) { if(this.ctx.status === FULFILLED) { try{ onFulfilled(this.ctx.value) } catch(err) { console.log('catch error', err) } } } MyPromise.prototype.then = function(onFulfilled, onRejected) { // 实现链式调用,同时确保每次返回的都是一个新的promise。 const _promise = new MyPromise((resolve, reject) => { if(this.ctx.status === PENDING) { try{ const result = onFulfilled(this.ctx.value) resolvePromise(promise2, x, resolve, reject); } catch(err) { reject(err) }
}
P.then().catch()
const p = new MyPromise((resolve, reject) => { setTimeout(() => { resolve(1) }, 1000) }) p.then(res => {
})
==========resolve改动=============== const onFulfilledQueue = []
const resolve = (value) => { if(ctx.status === PENGDING) { ctx.status = FULFILLED ctx.value = value if(onFulfilledQueue.length) { onFulfilledQueue.forEach(fn => fn()) } } }
==========then改动=============== MyPromise.prototype.then = function(onFulfilled, onRejected) { const _promise = new MyPromise((resolve, reject) => { if(this.ctx.status === PENDING) { onFulfilled && this.ctx.onFulfilledQueue.push(onFulfilled) onRejected && this.ctx.onRejectedQueue.push(onRejected) } else if(this.ctx.status === FULFILLED) { const res = onFulfilled(this.ctx.value) resolvePromise(x, resolve, reject) } else if(this.ctx.status === REJECTED) { onRejected(this.ctx.reason) } }) return _promise }
function resolvePromise(res, resolve, reject) { if(res instanceof MyPromise || (res?.then && type res.then === 'function')) { res.then(resolve) } else { resolve(res) } }
var p = new Promise((resolve, reject) => {resolve(1)}).then(res => {console.log(res); return p})
// 报错信息 Uncaught (in promise) TypeError: Chaining cycle detected for promise #
// 例子 new Promise((resolve, reject) => { resolve(1) }).then(res => console.log('then', res)).catch(err => {console.log('catch', err); return err}).then(res => console.log('then2', res)).catch(err => {console.log('catch2', err)})
// then改版 MyPromise.prototype.then = function(onFulfilled, onRejected){ const _promise = new Promise((resolve, reject) => { const { status, value, onFulfilledQueue, onRejectedQueue } = this.ctx;
}
// 例子 new Promise((resolve, reject) => { resolve(1)
}).then().then().then().then().then(res => console.log('then5', res))
// catch new Promise((resolve, reject) => { reject(1) }).then(res => console.log('then', res)).catch().then(res => {console.log('then2', res); return res}).catch(err => {console.log('catch2', err)})
// then改动 MyPromise.prototype.then = function(onFulfilled, onRejected){ onFulfilled = onFulfilled && typeof onFulfilled === 'function' ? onFulfilled : (value) => value; onRejected = onRejected && typeof onRejected === 'function' ? onRejected : (reason) => { throw reason };
}
MyPromise.prototype.resove = function(params) { const _promise = new MyPromise((resolve, reject) => { if(params instanceof MyPromise || typeof params?.then === 'function') { params.then(resolve) } else { resolve(params) } }) return _promise }