javascript基础修炼(7)——Promise,异步,可靠性 (3)

如果没有链式调用,第三节中的多步骤的伪代码可能是如下的样子:

//为了聚焦核心逻辑,下面的伪代码省略了onReject的回调 promiseA = new Promise(function(resolve, reject){ //A带着控制器开始执行 A(resolve,reject); }); promiseA.then(function(resA){ //A执行结束以后,开始判断B到底是否要执行 promiseB = new Promise(function(resolveB, rejectB){ //如果B需要执行,则分配两个储物柜,并派发状态控制器,B带着A返回的数据resA开始执行 B(resA,resolveB,rejectB); }); promiseB.then(function(resB){ //B执行结束以后,开始判断C到底是否要执行 promiseC = new Promise(function(resolveC, rejectC){ //如果C需要执行,则分配两个储物柜,并派发状态控制器,C带着B返回的数据resB开始执行 C(resB, resolveC, rejectC); }); //...如果有D的话 }) });

在逻辑流程中仅仅有3个步骤的时候,回调地狱的苗头就已经显露无疑了。Promise被设计用来解决回调嵌套过深的问题,如果只能按上面的方法来使用的话显然是不能满足需求的。如果可以支持链式调用,那么上面代码的编写方式就变成了:

//为了聚焦核心逻辑,下面的伪代码省略了onReject的回调 promiseA = new Promise(function(resolve, reject){ //A带着控制器开始执行 A(resolve,reject); }); promiseA.then(function(resA){ //在使用then方法向A的储物柜里存放事件的同时,也生成了自己的储物柜 return new Promise(function(resolveB, rejectB){ B(resA, resolveB, rejectB); }); }).then(function(resB){ return new Promise(function(resolveC, rejectC){ C(resB, resolveC, rejectC); }); }).then(function(resC){ //如果有D动作,则继续 })

很明显,当流程步骤增多时,支持链式调用的方法具有更好的扩展性。下一节讲一下Promise最关键的链式调用环节的实现。

五. Promise如何支持链式调用 基本原理

如果需要then方法支持链式调用,则Promise.prototype.then这个原型方法就需要返回一个新的promise。事实上即使在最初的时间节点上来看,后续注册的任务也符合在未来某个不确定的时间会返回结果的特点,只是多了一些前置条件的限制。返回新的promise实例是非常容易做到的,但从代码编写的逻辑来理解,这里的promise到底是什么意思呢?先看一下基本实现的伪代码:

//为简化核心逻辑,此处只处理Promise状态为PENDING的情况 //同时也省略了容错相关的代码 Promise.prototype.then = function(onFulfilled, onRejected){ let that = this; return new Promise(function(resolve, reject){ //对onFulfilled方法的包装和归类 that.onFulfilledCallbacks.push((value) => { let x = onFulfilled(value); someCheckMethod(resolve, x, ...args); }); //对onRejected方法的包装和归类 that.onRejectedCallbacks.push((reason) => { let x = onRejected(reason); someCheckMethod(reject, x, ...args); }); }); };

可以看到在支持链式调用的机制下,最终被添加至待执行队列中的函数并不是通过then方法添加进去的函数,而是通过Promise包装为其增加了状态信息,并且将这个状态改变的控制权交到了onFulfilled函数中,onFulfilled函数的返回结果,会作为参数传入后续的判定函数,进而影响在执行resolve的执行逻辑,这样就将新promise控制权暴露在了最外层。

所以,then方法中返回的promise实例,标记的就是添加进去的onFulfilled和onRejected方法的执行状态。这里的关键点在于,onFulfilled函数执行并返回结果后,才会启动对于这个promise的决议。

支线故事

在新的链式调用的支持下,上面的故事流程就发生了变化。当B前来登记事件时,执行器说我们这现在推出了一种委托服务,你想知道那个储物柜的最新动态,就把你的电话写在字条上放在对应的抽屉里,之后当这个抽屉打开后,我们就会把它返回的信息发送到你留在字条上的号码上,我们会给你提供一个智能储物柜(带有this._onFulfillCallbacks抽屉和this._onRejectedCallbacks抽屉)和一个控制器,这样别人也可以关注你的动态,但你的控制器暂时不能用,我们将某个消息发送到你留的手机号码上时,才会同步激活你的控制器功能,但它也只能作用一次。

六. resolve(promise)

再来考虑一种特殊的场景,就是当A动作调用resolve(value )方法来改变状态机的状态时,传入的参数仍然是一个PENDING状态的promise,这相当于A说自己已经完成了,但是此时却无法得到执行结果,也就不可能将结果作为参数来启动对应的apromise._onFulfilledCallbacks队列或者apromise_onRejectedCallbacks队列,此时只能先等着这个promise改变状态,然后才能执行对A动作的决议。也就是说A的决议动作要延迟到这个新的promise被决议以后。用伪代码来表示这种情况的处理策略就是如下的样子:

//内部方法 let that = this;//这里的this指向了promise实例 function resolve(result){ if(result instanceof Promise){ return result.then(resolve, reject); } //执行相应的缓存队列里的函数 setTimeout(() => { if (that.status === PENDING) { that.status = FULFILLED; that.value = result; that.onFulfilledCallbacks.forEach(cb => cb(that.result)); } }); }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxwgs.html