🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
- resolve的是一个promise - then中return了一个promise - 防止循环引用 - 延迟设计 - 初衷猜想 - 关于异常捕获 - 值的穿透 [TOC] ## pre-notify previously : - [Promise深度学习---我のPromise/A+实现](https://juejin.im/post/5a59e78ff265da3e3e33ba6e) - [异步发展简明指北](https://juejin.im/post/5a6212386fb9a01ca5608de3) 最近发现掘金上又多了很多promise相关的文章,于是乎把一年前写的东东拿出来看了看,又理了理,于是乎就有了这么一篇文。 > 时间获取并不是平白的流过,TA能让有些事逐渐变得清晰,只要没有彻底放手 ## resolve的是一个promise promise允许resolve里可以是一个promise。 比如说 假如我们有一个promise1,这个promise1`resolve`的也是一个promise,我们姑且称之为promise2,那么这个promise2的结果将作为我们promise1的结果(状态和值)。 ``` let p = new Promise(function(resolve,reject){ resolve(new Promise(function(resolve,reject){ resolve('a'); })) }); >>> p.value <<< a ``` 并且这个promise可以无限的在resolve中嵌套下去。 而我们只需要记住我们最终拿到的promise的结果是**最里层的promise的结果**即可。 --- 实现 ``` function resolve(value){ // value可能是一个promise if(value!==null&&(typeof value==='object'||typeof value==='function')){ return value.then(resolve,reject); } ... } ``` 思维模型图大概长这样 ![](https://box.kancloud.cn/8bbfd238c1d3487194c49f5afba53856_698x84.png) 就像一个个钩子,绑定和定义的顺序是从右往左,执行时是从左往右 ## then中return了一个promise 正常情况下如果then中return的是一个普通值,那么会走下一个then中的成功回调,并且这个return的值会作为回调的参数传入。 但如果是一个promise,则会根据这个promise的状态来决定走下一个then中的哪个回调,并且以这个promise的值作为回调的参数传入。 ``` p.then(function(data){ return new Promise(function(resolve,reject){ resolve(100); }) }).then(function(data){ console.log(data); }) <<< 100 ``` 其次这个返回的promise里也可像上面的说过的一样在这个promise里的resolve里继续嵌套promise ``` p.then(function(data){ return new Promise(function(resolve,reject){ resolve(new Promise(resolve,reject){ resolve(200); }); }) }).then(function(data){ console.log(data); }) <<< 200 ``` --- 实现: 这大概是Promise实现最难的一部分,主要是通过一个`resolvePromise`的方法来支持我们上述的功能,先上完整代码 ``` function resolvePromise(p2,x,resolve,reject){ // 注意:有可能解析的是一个第三方promise if(p2 === x){ //防止循环引用 return reject(new TypeError('Error:循环引用')); //让promise2失败 } let called; // 防止第三方的promise出现可能成功和失败的回调都调用的情况 if(x!==null||(typeof x === 'object')||typeof x === 'function'){ // 进来了只能说明可能是promise try{ let then = x.then; if(typeof then === 'function'){ then.call(x,function(y){ if(called) return; //这里可能交由的是第三方promise来处理,故可能被调用两次 called = true; // p.then(function(){return new Promise(){resolve(new Promise...)}}) return中的promise的resolve又是一个promise,即y又可能是一个promise resolvePromise(p2,y,resolve,reject); },function(err){ if(called) return; called = true; reject(err); }); }else{ resolve(x); //可能只是组键值对,像这样{then:1} } }catch(e){ // Object.define({},'then',{value:function(){throw Error()}}) if(called) return; // 防止第三方promise失败时 两个回调都执行 导致触发两次reject注册的回调 called = true; reject(e); } }else{ //说明是个普通值 让p2成功 resolve(x); } } ``` 其中尤其要重视的是这一部分 ``` if(typeof then === 'function'){ then.call(x,function(y){ resolvePromise(p2,y,resolve,reject); },function(err){ if(called) return; called = true; reject(err); }); } ``` 这里,此时then中成功回调的这个y参数即有可能是我们所说的resolve里嵌套promise的情况, 故我们需要将这个y传入`resolvePromise`方法再次进行解析,这样不断递归,直到这个y变成一个普通值,我们以这个普通值来resolve我们then中返回的promise。 ## 防止循环引用 如果我们在一个`then`中return了一个promise,且这个promise还恰巧是then后返回的promise本身,那么这个then返回的promise永远不可能会转换状态。So为了防止出现这种情况我们会直接reject it。 ``` 如果then中返回的promise是它本身就reject it let p = new Promise(function(resolve,reject){ resolve(); }); var p2 = p.then(function(){ return p2; //<---看这里!! }); p2.then(function(){ },function(e){ console.log(e); //会走这里 }); ``` --- 实现: 主要是`resolvePromise`方法中的这么一句 ``` if(p2 === x){ //防止循环引用 return reject(new TypeError('Error:循环引用')); //让promise2失败 } ``` ## 延迟设计 在promise的设计中,即使promise里没有异步resolve,通过promise`then`注册的回调也只会在标准的同步代码执行完成后才会执行 ``` let p = new Promise(function(resolve,reject){ resolve(222); }); p.then(function(data){ console.log(data); }) console.log(111); <<< 111 222 ``` 我们可以在代码中这样实现 ``` ... promise2 = new Promise(function(resolve,reject){ // 因为返回值可能是普通值也可能是一个promise,其次也可能是别人的promise,故我们将它命名为x, setTimeout(function(){ try{ let x = onFulfilled(self.value); resolvePromise(promise2,x,resolve,reject); }catch(e) { reject(e); } }); }); ... ``` 即让then回调执行之前套上一层`setTimeout`,让它在下一轮执行。 (但实际上原生的promise实现是将其作为微任务而不是宏任务执行的)。 ### 初衷猜想 这么设计很重要的一个原因在我看来是因为执行then回调时,我们会调用`resolvePromise`这个方法,而调用这个方法时我们需要将then新返回的promise传入,但我们能在一个new的过程中拿到自己吗? 像这样 ``` let a = new A(){ console.log(a); } ``` 这样显然拿到的是`undefined`。 So,为了拿到这个新返回的promise,故我们在外面套了一层`setTimeout`这样的东东。 ## 关于异常捕获 一个then,只要它有return,那么下一个then就会走成功的回调,即使这个return的是一个状态为失败态的promise。 ``` p.then(function(data){ return new Promise(function(resolve,reject){ reject('失败'); }) }) .then(function(data){ console.log('会走成功'); },function(err){ }) <<< 会走这里 ``` 只有一种情况下一个then会走失败的回调,那就是此次的回调执行是抛出了异常, ``` p.then(function(data){ throw Error('出现错误!') }) .then(function(data){ console.log('会走成功'); },function(err){ console.log(err) }) <<< 出现错误! ``` --- 实现: 我们在两个地方使用过try catch 一个是`executor`执行的时候 ``` try{ // 正因为executor执行是同步的,故我们能使用try catch executor(resolve,reject); }catch(e){ reject(e); } ``` 一个是我们`then`所注册的回调执行的时候 ``` ... promise2 = new Promise(function(resolve,reject){ setTimeout(function(){ try{ let x = onFulfilled(self.value); resolvePromise(promise2,x,resolve,reject); }catch(e) { reject(e); } }); }); ... ``` ## 值的穿透 promise允许我们使用空then(即使这样做并没有什么意义),而这些省略了回调的then原本改接收的参数会被向下传递直到遇到一个不是空的then。 ``` var p = new Promise(function(resolve,reject){ resolve(100) }); p.then().then().then(function(data){ console.log(data); },function(err){ console.log(err) }); <<< 100 //也允许异常向下传递 ``` --- 实现: ``` Promise.prototype.then = function(onFulfilled,onRejected){ // 成功和失败默认不传,则让他们穿透直到有传值的then onFulfilled = typeof(onFulfilled)==='function'?onFulfilled:function(value){ return value; }; onRejected = typeof(onRejected)==='function'?onRejected:function(err){ throw err; }; ... ``` ## 源码 仓库:[点我获取](https://github.com/fancierpj0/iPromise) --- > 时间或许并不是平白的流过,只要一直在尝试着,TA似乎能让有些事逐渐变得清晰 --- end ---