手寫Promise看著一篇就足夠了
阿新 • • 發佈:2020-09-01
[TOC]
## 概要
本文主要介紹了Promise上定義的api的特性,以及手寫如何實現這些特性。目的是把學習過程中的產出以部落格的方式輸出,鞏固知識,也便於之後複習
## 部落格思路
mdn上搜索Promise,瞭解類和api的定義:
- 定義了哪些屬性,分別代表什麼含義
- api需要傳什麼引數,返回什麼值,可能丟擲什麼異常
- 看官方給出的用例,猜想內部可能的實現
- 編寫原始碼,用官方用例驗證檢視返回值是否一致
## API的特性與手寫原始碼
### 建構函式
- promise有狀態**pending、rejected**和**resolved**,所以應該有個變數來儲存狀態
- 建構函式引數**excutor**是個**同步執行**的回撥函式,函式執行的引數是兩個函式resolved和rejected,所以promise內部需要定義兩個函式,並且在構造行數中執行excutor的地方傳入
- .then中會傳入回撥函式onResolved和onRejected,在resolved和rejected內會分別會觸發對應的回撥函式,所以需要兩個陣列儲存then中傳進來的回撥
- resolved和rejected**只能執行一次**,執行後promise的狀態會改變,且引數會傳遞給回撥函式
- onRejected和onResolved**非同步執行**
- excutor執行拋異常會直接執行rejected,所以**excutor的執行需要catch錯誤**
```javascript
const PENDING = "PENDING";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(excutor){
// promise內部儲存著狀態值
this.status = PENDING;
this.data = null;
// then方法傳進來的回撥函式此儲存
this.onResolvedList = [];
this.onRejectedList = [];
let resolved = (value) => {
// resolved函式只能執行一次,所以先判斷狀態是不是pending
if(this.status !== PENDING){
return;
}
// 變更狀態為resolved
this.status = RESOLVED;
// 資料為傳進來的值
this.data = value;
// 判斷是否已經有onResolved回撥已經穿入,有則非同步執行
if(this.onResolvedList.length > 0){
setTimeout(() => {
this.onResolvedList.forEach(onResolved => {
onResolved(value);
});
}, 0);
}
}
let rejected = (reason) => {
if(this.status !== PENDING){
return
}
this.status = REJECTED;
this.data = reason;
if(this.onRejectedList.length > 0){
setTimeout(() => {
this.onRejectedList.forEach(onRejected => {
onRejected(reason);
});
});
}
}
try{
// 執行器函式同步執行,且引數為promise內定義的resolve和rejected
excutor(resolved, rejected);
}catch(error){
// 如果執行器函數出錯直接執行rejected
this.rejected(error);
}
}
```
### then
- then會接受兩個回撥函式onResolved和onRejected
- onResolved和onRejected會非同步呼叫
- then會返回一個新的promise物件
- then的引數如果沒傳,那麼value和reason繼續向下傳遞
- 如果執行then的時候,promise的狀態還是pending,那麼只儲存回撥,並且**確保回撥執行後能修改新的promise的狀態**
- 如果觸發的對應的回撥函式執行拋異常,那麼返回的新的回撥函式狀態為rejected,reason則會catch到的error
- 如果觸發的對應回撥函式執行返回值不是promise物件,那麼返回新的promise狀態為resolved,value則為傳入then的回撥的返回值
- 如果觸發的對應回撥返回值是promise物件,那麼新的promise返回值的狀態取決於改回調返回的promise
```javascript
MyPromise.prototype.then = function(onResolved, onRejected){
// 如果沒有傳onResolved,則設定onResolved為返回value的函式
onResolved = typeof onResolved === "function" ? onResolved : value => value
// 如果沒有傳onRejected,則設定onRejected為拋處reason的函式
onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason}
return new MyPromise((resolved, rejected) => {
// 傳入要執行的回撥函式
let callBackExcu = (callback) => {
try{
let result = callback(this.data);
if(result instanceof MyPromise){
// 如果回撥返回值還是promise則then返回的promise的狀態取決於回撥的返回的promise,成功就執行resolved失敗就執行rejected
result.then(resolved, rejected);
}else{
// 如果回撥的返回值不為promise則新的promise狀態為resolved
resolved(result)
}
}catch(error){
// 如果回撥執行拋處異常,則新的promise狀態為rejected
rejected(error);
}
}
if(this.status === PENDING){
// 如果狀態為pending則儲存回撥且確保回撥執行後能修改當前返回的promise的狀態
this.onResolvedList.push((value) => {
callBackExcu(onResolved)
});
this.onRejectedList.push((reason) => {
callBackExcu(onRejected)
});
}else{
// 如果狀態不為pending則根據狀態執行對應的回撥,且修改當前promise的狀態
switch(this.status){
case REJECTED:
// onRejected非同步執行
setTimeout(() => {
callBackExcu(onRejected);
});
break;
case RESOLVED:
// onResolved非同步執行
setTimeout(() => {
callBackExcu(onResolved);
});
break;
}
}
});
}
```
### catch
catch和then其實差不多,不同點在於傳入的引數只有onRejected,所以
```javascript
MyPromise.prototype.catch = function(onRejected){
// catch與then的不同點在於傳入的引數不一樣,不需要傳onResolve
return this.then(null, onRejected);
}
```
### Promise.resolved
- resolved會返回一個promise物件
- 如果傳入的引數本就是一個primise物件則直接返回
- 如果是一個包含“then”方法的物件,返回新的promise物件,且狀態取決於then函式的執行,如果then的執行中拋錯,則新的promise狀態為rejected
- then的引數為兩個回撥函式resolved和rejected
- 如果傳入引數value既不是promise的例項,也不是具備then函式的物件,則返回一個新的promise物件且改物件data就為value
```javascript
MyPromise.resolve = function(value){
if(value instanceof MyPromise){
// 如果傳入的引數本就是一個primise物件則直接返回
return value;
}else if(typeof value.then === "function"){
return new MyPromise((resolved, rejected) => {
try{
// then的引數為兩個回撥函式resolved和rejected
value.then(resolved, rejected);
}catch(error){
// 如果then的執行中拋錯,則新的promise狀態為rejected
rejected(error);
}
});
}else{
// 如果傳入引數value既不是promise的例項
return new MyPromise((resolved, rejected) => {
resolved(value);
});
}
}
```
### Promise.rejected
- 接受引數reason,返回一個狀態為rejected的data為reason的promise例項
```javascript
MyPromise.reject = function(reason){
return new MyPromise((resolved, rejected) => {
rejected(reason);
});
}
```
### Promise.all
- 接收的引數是需要滿足[可迭代協議](https://blog.csdn.net/jianghao2016/article/details/108185212),否則會拋錯
- 返回值是個promise
- 如果傳入的引數是個空的可迭代的物件,則返回一個狀態為resolved的可promise例項,data為空陣列,
```javascript
Promise.all([]) // P