-
Notifications
You must be signed in to change notification settings - Fork 0
Description
前言
promise是我们日常工作中几乎都要用到的一个api,它的出现帮助我们解决了异步问题,至于回调地狱,如果层级多了,promise还是会产生回调地狱,不过我们可以借助async/await一起使用来解决回调地狱的问题。使用了这么久的promise,对于promise如何实现的也没有一窥全貌,所以这次跟大家一起分享它里面的实现。
EventLoop执行机制
在讲解promise原理之前,我们先来了解eventLoop与宏微任务。
微任务
-
js自带api
- queuemMicrotask
- Promise
-
需要判断环境
- process.nextTick(node)
- MutationObserver(浏览器端)
宏任务
- js代码
- setTimeout
- setIntervel
- setImmediate
事件循环机制的执行过程简单提一点:js代码从上至下执行,遇到宏(微)任务,将宏(微)任务存在宏(微)任务事件队列中等待,等待js代码执行完后,再到微任务事件队列中查找是否还有任务在等待执行,如果微任务事件队列没有任务,同理,到宏任务事件队列执行。直到宏任务微任务执行完毕。
本次分享的重点不在这,有兴趣想了解更多请查阅
从一个问题来看promise
- 输出下面代码的打印顺序:
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的标记
-
p0(() => { console.log(0); return Promise.resolve(4)})进入异步队列中,此时异步队列[p0];
-
p1进入异步队列中,此时异步队列[p0,p1]。
-
执行p0,输出0。return Promise.resolve(4)则是转换成 () => p4.then(完成p0的状态变化)添加到异步队列中,此时异步队列[p1, () => p4.then(完成p0的状态变化)]
-
输出1,由于p1状态变为fulfilled,p2进入异步队列,[() => p4.then(完成p0的状态变化), p2]
-
执行p4.then(完成p0的状态变化),(完成p0的状态变化即pres = (res) => res), pres进入异步队列,此时异步队列为[p2, pres]
-
输出2,p2状态变为fulfilled,p3进入异步队列,此时异步队列为[pres, p3]
-
执行pres,p0状态变为fulfilled,后续打印res的then回调进入异步队列中,此时[p3, () => console.log(res)]
-
输出3,p5进入异步队列中
-
输出4
-
输出5,p6进入异步队列中
-
输出6
实现一个promise
使用方式
- 常规
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))
- 异步resolve
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))
- 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。
- new Promise()抛出异常
new MyPromise((resolve, reject) => {
throw new Error()
})
function MyPromise(executor) {
...
try {
executor(resolve, reject)
} catch(err) {
reject(err)
}
}
- 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链式调用
- 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 #<Promise>
- 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 实现细节
V8源码补充篇
Promise V8 源码分析(一)
chrome v8 promise源码
promiseA+规范