- 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 ---