Promise实现的基本原理(二)
栏目: JavaScript · 发布时间: 5年前
内容简介:距离我上次写的那篇参考链接: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 及其实现原理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。