ES6的Promise已经被大量的用到了项目当中,但我们要知其然,更要知其所以然,所以让我们来解析一下Promise其中的实现原理吧。(文章可能有错误,还请各位大胆指证)。
一:回调改装为链式调用
我个人认为Promise最关键是功能就是能把各种回调改写为链式调用的形式,避免陷入回调地狱。但是Promise的实现底层是离不开回调的,话不多说,先上代码。
class MyPromise {
constructor(fn) {
this.callBack = function(){}
fn((res) => {
this.callBack(res)
})
}
then(onFulfilled) {
this.callBack = onFulfilled
}
}
const mp = new MyPromise(res => {
setTimeout(() => {res
res(1);
}, 1000)
})
mp.then(res => {
console.log(res);
})
好了,现在我们在控制台使用node运行一下我们的代码
在延迟一秒后成功的打印出了1,说明我们的成功了吗???显然不是,虽然我们原本需要通过回调***onFulfilled***来处理的东西,修改成了通过then方法处理,但是如果连续执行几次次mp.then你会发现,控制台仅会出现1次打印,正常来讲,then回调是可以多次调用的。
二:可多次调用的then函数
实际上Promise中callBack是以数组的形式存在的,而不是单个存在的,以此才能实现多次调用,让我们改写一下以上代码。
class MyPromise {
constructor(fn) {
this.callBacks = []
fn((res) => {
this.callBacks.forEach(callBack => {
callBack(res)
})
})
}
then(onFulfilled) {
this.callBacks.push(onFulfilled)
}
}
const mp = new MyPromise(res => {
setTimeout(() => {
res(1);
}, 1000)
})
mp.then(res => {
console.log(res);
})
mp.then(res => {
console.log(res);
})
ok,再次使用node运行代码,控制台可以打印出两个“1”了,这一步完成。
三:同步/异步处理(状态处理)
先让我们来看一个原生Promise的一个很简单的例子:
显然这理所应当会直接打印一个“1”出来,但是我们的MyPromise现在是不具备处理这种同步逻辑的能力的,让我们来看看如果MyPromise运行相同的逻辑,会出现什么情况。
兄弟,离大谱了!这么简单的逻辑都处理不了谁用啊!不慌,让我们回到代码,搞清楚为什么控制台没有输出?我们先来看看第二节里我们用到的代码,请特别注意我们创建mp时用了一个setTimeout函数。
const mp = new MyPromise(res => {
setTimeout(() => {
res(1)
}, 1000)
})
了解过js事件循环的朋友都知道setTimeout是一个宏任务,在当前事件中,微任务如果没有被清理干净的话,是不会进入到另一个事件循环(执行新的宏任务的),所以这时我们的“mp”对象得以在执行完constructor后,不立即执行fn,而是先执行了then方法,then方法将onFulfilled回调push进了callBacks队列里以后,当前循环微任务处理完毕,随后才开始执行了fn,让我们在代码中的几个关键位置加入几个log查看一下代码执行的顺序。


Promise会在fulfilled状态时直接调用回调,在pending状态将回调push入callBacks队列挂起,等状态改变以后再重新执行回调。
想要实现这一步还,必须借助一个中间函数来对相应的状态做处理,并且响应状态的变化,让我来修改代码。class MyPromise {
constructor(fn) {
this.callBacks = []
this.state = "pending"
this.value = null // 用于保存得到的数据
fn((res) => {
this.state = "fulfilled" // 修改状态
this.value = res // 保存得到的数据
this.callBacks.forEach(callBack => {
// callBack(res)
this._handle(callBack)
})
})
}
then(onFulfilled) {
function defaultCb() {
return false // 避免不传入回调时报错
}
// this.callBacks.push(onFulfilled)
new MyPromise(resolve => {
this._handle({
onFulfilled: onFulfilled || defaultCb,
resolve,
})
})
}
// 用于处理状态,和响应状态的改变
_handle(callBackItem) {
if(this.state === "pending") {
this.callBacks.push(callBackItem)
}
else if(this.state === "fulfilled") {
const { resolve, onFulfilled = defaultCb } = callBackItem
resolve(onFulfilled(this.value) || this.value)
}
}
}
const mp = new MyPromise(res => {
// setTimeout(() => {
res(1);
// }, 1000)
})
mp.then(res => {
console.log(res);
})
此时,再次运行代码,会看到控制台已经可以打印出“1”了!!!我们离大功告成又进一大步!此处的_handle是关键点,有了handle以后,我们才可以既在pending时,将callBack push到callBacks队列,也可以在“fn”执行完以后立即响应状态的变化,给出结果。
四:真正的链式调用
但是我们使用Promise时经常会强调他是链式调用的,仔细观察我们的代码,根本没有该功能,
我们一起来看一个典型的链式调用的例子!
从中我们可以看出以下两点信息:
- then函数一定会返回一个新的Promise对象(因为我们可以继续在then后面调用then)。
- 下一个then函数可以把上一次 then函数执行的结果拿到,并且能进行“加工后”继续传递给下一个then函数。
以上能力是我们目前MyPromise不具备的,想要满足这两点其实很简单,只需要在then方法中返回一个新的Promise对象即可,也就是在第三节中,我们修改完bug的代码中执行then方法时创建的Promise对象,return出去即可。继续修改代码。
好了,此时在打印一下控制台,是不是就出现了我们理想的123了叻,有没有觉得越来越简单了叻,加油加油加油。哦,对了现在我们的constructor中fn的处理太多了,这个不属于构建对象时的核心代码,而是处理回调的核心代码,为了避免代码污染,所以我们再重构一下代码,把fn提出来。
class MyPromise {
constructor(fn) {
this.callBacks = []
this.state = "pending"
this.value = null // 用于保存得到的数据
fn((res) => {
this._resolve(res)
})
}
then(onFulfilled) {
function defaultCb() {
return false // 避免不传入回调时报错
}
// this.callBacks.push(onFulfilled)
return new MyPromise(resolve => {
this._handle({
onFulfilled: onFulfilled || defaultCb,
resolve,
})
})
}
_handle(callBackItem) {
if(this.state === "pending") {
this.callBacks.push(callBackItem)
}
else if(this.state === "fulfilled") {
const { resolve, onFulfilled = defaultCb } = callBackItem
resolve(onFulfilled(this.value) || this.value)
}
}
_resolve(res) {
this.state = "fulfilled" // 修改状态
this.value = res // 保存得到的数据
this.callBacks.forEach(callBack => {
// callBack(res)
this._handle(callBack)
})
}
}
const mp = new MyPromise(res => {
res(1);
})
mp.then(res => {
console.log(res);
return res+1
})
打印一下没有问题,一切正常,进去下一节。
五:catch、finally
我们现在有了then的思想,再来看catch,其实他们本质上就是回调,我们以前写回调的成功和失败怎么写的呢?是不是像这样:
smoeCb(
success => {
// 成功的处理
},
fail => {
// 失败的处理
}
)
或者这样
smoeCb({
success: () => {
// 成功的处理
},
fail: () => {
// 失败的处理
}
})
Promise和第一种写法一模一样,他也是有成功和失败两种回调入参滴~,大家可以尝试运行一下以下代码
由此可见,then的入参本就至少是两个:一个成功的回调,一个失败的回调,完全就是一个套路嘛,冲!改代码去。
class MyPromise {
constructor(fn) {
this.callBacks = []
this.state = "pending"
this.value = null // 用于保存得到的数据
fn(
res => {
this._resolve(res)
},
// 加入错误的回调
err => {
this._reject(err)
}
)
}
then(onFulfilled, onRejected) {
const defaultCb = function () { return false }
return new MyPromise((resolve, reject) => {
this._handle({
onFulfilled: onFulfilled || defaultCb,
resolve,
onRejected: onRejected || defaultCb,
reject
})
})
}
_handle(callBackItem) {
const { resolve, onFulfilled = defaultCb, reject, onRejected = defaultCb } = callBackItem // 声明的东西比较多,就提上来了
const errorHandle = (e) => { // 错误的捕捉有两个地方用了,我的English又比较长,也提出来了
reject(e||onRejected(this.value) || this.value || "your promise is rejected by an unknown error") // 不好意思此处我想拽个洋文儿
}
try {
if(this.state === "pending") {
this.callBacks.push(callBackItem)
}
else if(this.state === "fulfilled") {
resolve(onFulfilled(this.value) || this.value)
}
else if(this.state === "rejected") {
errorHandle()
}
}catch(e) {
// 此处加入try-catch主要是为了处理fulfilled状态时onFulfilled回调中的错误
this.state = "rejected"
errorHandle(e)
}
}
_resolve(res) {
this.state = "fulfilled" // 修改状态
this.value = res // 保存得到的数据
this.callBacks.forEach(callBack => {
// callBack(res)
this._handle(callBack)
})
}
_reject(err) {
this.state = "rejected"
this.value = err
this.callBacks.forEach(callBack => {
// callBack(res)
this._handle(callBack)
})
}
}
const mp = new MyPromise((res, rej) => {
rej(1);
})
// 是不是有内维尔了
mp.then(
success => {
console.log("success:", success);
},
error => {
console.log("error:", error);
}
)
咳咳,此时已经可以在控制台打印一下错误的回调了“error: 1”哟。好,回到第三节我说过rejected状态的实现很简单,没错吧哈哈哈哈,然后又就是catch,实际上catch只是then的改写,相当之简单,马上贴代码
哈!有没有被震撼到,同理可得finally,但是有一点不一样,finally不需要新的回调,他只是在then或者catch执行完以后执行一段代码就行了,所以我们直接把onFulfilled,onRejected都作为finally的入参,见代码:
至此Promise的基本原理就结束了谢谢大家的阅读,欢迎多多指正。
附上整体代码:
class MyPromise {
constructor(fn) {
this.callBacks = []
this.state = "pending"
this.value = null // 用于保存得到的数据
fn(
res => {
this._resolve(res)
},
// 加入错误的回调
err => {
this._reject(err)
}
)
}
then(onFulfilled, onRejected) {
const defaultCb = function () { return false }
return new MyPromise((resolve, reject) => {
this._handle({
onFulfilled: onFulfilled || defaultCb,
resolve,
onRejected: onRejected || defaultCb,
reject
})
})
}
catch(onRejected) {
const err = this.then(null, onRejected)
return err
}
finally(cb) {
return this.then(cb, cb)
}
_handle(callBackItem) {
const { resolve, onFulfilled, reject, onRejected } = callBackItem // 声明的东西比较多,就提上来了
const errorHandle = (e) => { // 错误的捕捉有两个地方用了,我的English又比较长,也提出来了
reject(e || onRejected(this.value) || this.value || "your promise is rejected by an unknown error") // 不好意思此处我想拽个洋文儿
}
try {
if (this.state === "pending") {
this.callBacks.push(callBackItem)
}
else if (this.state === "fulfilled") {
resolve(onFulfilled(this.value) || this.value)
}
else if (this.state === "rejected") {
errorHandle()
}
} catch (e) {
// 此处加入try-catch主要是为了处理fulfilled状态时onFulfilled回调中的错误
this.state = "rejected"
errorHandle(e)
}
}
_resolve(res) {
this.state = "fulfilled" // 修改状态
this.value = res // 保存得到的数据
this.callBacks.forEach(callBack => {
// callBack(res)
this._handle(callBack)
})
}
_reject(err) {
this.state = "rejected"
this.value = err
this.callBacks.forEach(callBack => {
// callBack(res)
this._handle(callBack)
})
}
}