- 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);
}
...
}
```
思維模型圖大概長這樣

就像一個個鉤子,綁定和定義的順序是從右往左,執行時是從左往右
## 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 ---