Promise实现的基本原理(二)
栏目: JavaScript · 发布时间: 6年前
内容简介:距离我上次写的那篇参考链接:github上的完整实现:
距离我上次写的那篇 Promise实现的基本原理(一) 有十几天了(太懒了),这篇我想写Promise是如何实现串联调用then,解决回调地狱的。虽然说《深入理解ES6》这本书把Promise用法讲得很详细,但是自己理解起来还是有很多疑问(我好想上一篇也是这么说的)。
参考链接: JavaScript Promises ... In Wicked Detail
github上的完整实现: Bare bones Promises/A+ implementation
一、Promise可能会出现的异步情况
情况一:对resolve的调用是异步的
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolve')
console.log('Promise setTimeout')
}, 1000)
}).then(result => {
// 这个箭头函数我们叫 fn1
console.log(result)
}, error => {
// 这个箭头函数我们叫 fn2
console.log(error)
})
复制代码
情况二:对then的调用是异步的
let p1 = new Promise((resolve, reject) => {
resolve('resolve')
console.log('Promise setTimeout')
})
setTimeout(() => {
p1.then(result => {
// 这个箭头函数我们叫 fn1
console.log(result)
}, error => {
// 这个箭头函数我们叫 fn2
console.log(error)
})
}, 1000)
复制代码
情况三:调用resolve和调用then都是异步的
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolve')
console.log('Promise setTimeout')
}, 1000)
})
setTimeout(() => {
p2.then(result => {
// 这个箭头函数我们叫 fn1
console.log(result)
}, error => {
// 这个箭头函数我们叫 fn2
console.log(error)
})
}, 1000)
复制代码
第三种情况是前两种情况的结合,如果第一个 setTimeout
时间比第二个 setTimeout
时间长,那就是第一种情况;如果第二个 setTimeout
比第一个 setTimeout
的时间长,那就是第二种情况。了解 event-loop
的同学看着几种情况应该比较清晰,不了解的同学就看我下面的分析。
二、Promise三种状态是如何工作的
我从《深入理解ES6》 Promise
那章截取了一下内容:
每个 Promise
都会经历一个短暂的生命周期:先是处于进行中( padding
)的状态,此时操作尚未完成,所以他也是未处理( unsettled
)的;一旦异步操作执行结束, Promise
则变为已处理( settled
)的状态,操作结束后 Promise
可能会进入到以下两个状态的其中一个:
Promise Promise
Promise
的内部属性 [[PromiseState]]
被用来表示 Promise
的3中状态: "pedding"
、 "fulfilled"
、 "rejected"
。
实现代码:
function Promise(fn) {
var state = 'pending'; // 初始状态为padding
var value; // Promise处于被处理状态下保存下来的值
var deferred = null; // 延期执行函数(现在还看不出来它是函数)
/**
* 异步操作成功完成回调函数
* 在new Promise的时候被调用的(不清楚为什么的可以看上一篇博客)
*/
function resolve(newValue) {
// 如果在then的回调函数里面返回了一个Promise
if(newValue && typeof newValue.then === 'function') {
// 将这个Promise的then的两个回调参数的引用指向Promise的resolve和reject
newValue.then(resolve, reject);
return;
}
// 如果传进来的不是Promise
state = 'resolved'; // 将Promise的状态改成resolved(fulfilled)
value = newValue; // 保存return过来的值
if(deferred) { // 判断是否存在传进来的回调函数
handle(deferred); // 如果有,将我们传进来的回调函数交给handle
} //(不清楚为什么的可以看上一篇博客)
}
/**
* 异步操作失败/被拒绝回调函数
* 在 new Promise 的时候被调用的
*/
function reject(reason) {
state = 'rejected'; // 将Promise的状态改成rejected
value = reason; // 保存我们传进来的被拒绝原因
if(deferred) { // 和 resolve同理
handle(deferred);
}
}
function handle(handler) {
// handle被调用了,但是此时promise的状态还是处于出事的未处理状态
if(state === 'pending') {
deferred = handler; // 就将handler的引用先保存下来
return; // 不执行下面的代码
}
var handlerCallback; // Promise处理完成的状态只能是一种(fulfilled/rejected),所以用同一个变量保存回调的引用
if(state === 'resolved') { // 如果已经成功处理(fulfilled)
handlerCallback = handler.onResolved; // 引用是处理成功回调的引用
} else {
handlerCallback = handler.onRejected; // 如果不是,引用是处理失败回调的引用
}
if(!handlerCallback) { // 如果在调用then的时候没有给then传回调函数,即上一个if语句保存下来的引用是undefined的
if(state === 'resolved') { // 如果状态已经是resolved(fulfilled)的
handler.resolve(value); // 就调用hander的resolved(下一个then传入的成功回调函数)
} else {
handler.reject(value); // 否则,调用handler的reject(下一个then传入的第二个参数,即失败回调函数)
}
return; // 不执行下面的语句
}
// 如果本次调用的then有传入回调函数
var ret = handlerCallback(value);
handler.resolve(ret); // 就给
}
this.then = function(onResolved, onRejected) {
return new Promise(function fn3 (resolve, reject) { // then 返回一Promise
// 返回的Promise把then传进来的两个回调和本省的两个回调都丢给handle处理
handle({
onResolved: onResolved,
onRejected: onRejected,
resolve: resolve,
reject: reject
});
});
};
fn(resolve, reject);
}
复制代码
回调函数引用分析:
fn函数的引用在上一篇已经分析过了,这一片重点分析在then中返回Promise给handle函数传过去的对象属性引用
-
onResolved: 在我们用Promise对象调用then的时候传过来的第一个参数 fn1 (我在注释里给了个名字好区分,就不像第一篇那样一个一个拆出来了,有点懒)。 -
onReject: 在我们用Promise对象调用then的时候传过来的第二个参数fn2。 -
resolve:then返回的Promise对象传过去的fn3, 当fn3被调用时Promise传给它的第一个参数就是resolve的引用,所以resolve(这是key) 的引用是Promise的内部函数resolve(这是function)。当然,每一次new Promise都会是一个新的对象,所以内部的resolve的引用也是不一样的。 -
reject:reject和resolve同理,指向Promise内部函数reject。
handle
函数分析
1、 handle
参数分析
handle
函数的调用还是在用户调用 then
的时候调用,只不过这次 then
函数返回了一个 Promise
,给 handle
函数传入的值既保留了本次调用 then
传入的两个回调函数的引用,也保留了新的 Promise
的回调函数引用。
2、结合三种状态分析
(1) "pending"
状态:
当调用 handle
函数时 Promise
处于 padding
对应的情况是 情况一
,此时用户创建了 Promise
对象后立即调用了 then
函数,但 resolve
还没有被处理,所以 Promise
状态没有改变, then
的回调函数需要等待 resolve
被处理了才可以执行。所以当 padding
状态的时候,将 handler
这个参数交给 deferred
暂存。
(2) "resolved"
/ rejected
状态:
一秒后, resolve
回调函数被调用了(关于 resolve
的指向可以看第一篇), Promise
的 state
被置为 "resolved"
了,然后只要 deferred
有指向,就再一次交给 handle
函数处理一次。
此时 Promise
的状态是 "resolved"
,将 handlerCallback
的引用指向fn1,又将 handleCallback
返回的值保存在了 ret
中,同时又传给 then
返回的 Promise
的 resolve
,让返回的 Promise
处于已处理状态(只有当 resolve
被执行了, then
的回调函数才会执行,才能够实现 then
的无限串联调用),也就是一下这种情况:
let p = new Promise((resolve, reject) => {
resolve(42)
})
p.then().then().then(result => {
console.log(result) // 控制台输出42
})
复制代码
三、如何实现 then
的串联使用
上面那一点其实就可以看出, Promise
是通过在 then
函数里面返回了一个 Promise
,实现了 Promise
的 then
的串联使用。但上面的那情况并没有真正体现 Promise
的作用,请看以下代码:
new Promise((resolve, result) => { // Promise1
resolve(1)
}).then(result => { // Promise1的then
console.log(result) // 1
return new Promise((resolve, reject) => { // Promise3
resolve(2)
})
}) // 此处得到Promise2
.then(result => { // Promise2的then
console.log(result) // 2
return 3
}) // 此处得到Promise4
.then(result => { // Promise4的then
console.log(result) // 3
}) // 此处得到Promise5
复制代码
在实现代码里面可以看到 then
时返回了一个新的 Promise
的,然后再看上面的代码,在调用 then
的第一个回调函数回调的时候,我们给他返回了一个参数,一种是 Promise
对象,一种是返回一个数值,到这里我的疑问是 then
内部返回的 Promise
是如何拿到这些值的,以下分析:point_up_2:上面的代码
Promise个数分析
上面那段代码里里外外有5个Promise,先起个小名,分别是:
new then then then then
1、Promise1:
handler
参数四个属性指向:
-
onResolved: Promise1的then的第一个参数(回调函数) -
onRejected: Promise1的then的第二个参数(回调函数) -
resolve: Promise2的内部函数resolve -
reject: Promise2的内部函数reject
Promise1当前的状态是 "resolved"
then
被调用,所以 handle
被调用blablabla~
handleCallback
指向 handler.onResolved
各种判断赋值后到了 handle
的最后两行,此时 ret
接收到 handleCallback
返回的值,然后这个值是Promise3。接着调用了 handle.resolve
,并且给它传了 ret
,而 handle.resolve
指向的是Promise2的 resolve
,所以接下来就跳到Promise2了。
2、Promise2:
handler
参数四个属性指向:
-
onResolved: Promise2的内部函数resolve -
onRejected: Promise2的内部函数reject -
resolve: Promise4的内部函数resolve -
reject: Promise4的内部函数reject
Promise2的 resolve
在Promise1的 handle
函数的最后一条语句被调用,并且传入的 ret
是一个 Promise
(Promise3)
所以Promise2的 resolve
函数中的第一个语句成立,但Promise2的状态不改变, newValue
指向的是Promise3,需要等待Promise3被处理。
接着Promise3的 then
被调用,所以接下要调到Promise3了。
3、Promise3
handler
参数四个属性指向:
-
onResolved: Promise2的内部函数resolve -
onRejected: Promise2的内部函数reject -
resolve: Promise3的then返回的Promise的内部函数resolve(在当前例子不是很重要,忽略) -
reject: Promise3的then返回的Promise的内部函数reject(在当前例子不是很重要,忽略)
在Promise2调用Promise3的 then
的时候,Promise3的状态未知,但不管怎样,只要 Promise
的状态改变了,就会调用一次handle函数。
某个时刻Promise3的状态变为了 "resolved"
/ "rejected"
或者在Promise2调用它的时候就已经是 "resolved"
/ "rejected"
状态
还是到 handl
e函数的最后两行,和Promise1的一样, handler.onResolved
会被执行,并且把Promise3的 resolve
回调时得到的值 value
传给 Promise2
的内部函数 resolve
,Promise2变为了 "resolved"
状态,并且的到了Promise3被处理后的 value
。
Promise3此时的任务已经完成了,被处理了,并且把得到的 value
传给了Promise2,拜拜:wave:。
4、回到Promise2
handler
参数四个属性的指向还是和之前一样,在Promise3被处理之后,Promise2的状态也会跟着Promise3的状态变化,并且拿到了Promise3的 value
,所以先在Promise2的 value
是2
Promise2状态变为已处理之后,不管什么时候调用 then
, then
的两个回调函数其中一个会被回调,所以 Promise
的这种处理,会让我这种菜:chicken:觉得 then
的回调函数里面 return
的 Promise
是变成了 then
的 return
,但其实Promise做的事情只是保存值而已。
5、Promise4
Promise4的 handle
r参数属性指向其实和Promise2是差不多的,和Promise2不同的是: ret
的到不是一个 Promise
,而是一个具体的值。也就是说 newValue = 3
,newValue不是 Promise
就好办了,把状态改成 "resolved"
,保存一下 return
过来的值,其他和最开始的 Promise
处理方式一样,完事。
总结
Promise
能够串联调用 then
,还是保存值和各种引用的转换, Promise
大法好!
以上所述就是小编给大家介绍的《Promise实现的基本原理(二)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Docker实现原理之 - OverlayFS实现原理
- 微热山丘,探索 IoC、AOP 实现原理(二) AOP 实现原理
- 带你了解vue计算属性的实现原理以及vuex的实现原理
- Docker原理之 - CGroup实现原理
- AOP如何实现及实现原理
- webpack 实现 HMR 及其实现原理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Powerful
Patty McCord / Missionday / 2018-1-25
Named by The Washington Post as one of the 11 Leadership Books to Read in 2018 When it comes to recruiting, motivating, and creating great teams, Patty McCord says most companies have it all wrong. Mc......一起来看看 《Powerful》 这本书的介绍吧!