手撕 Promise

前言

理解一個東西最好的辦法之一就是動手本身寫,So,他(Promise)來了。廢話很少說,來看如何實現。面試

「五嶽歸來不看山,黃山歸來不看嶽。」但願看完這篇,你就不用再去看其餘 Promise 的實現原理了。數組

Promise 解析

先來看Promise用法:promise

new Promise((resolve, reject) => {
    resolve('hello'); // or reject('hello')
})
 .then(res => {})
 .catch(err => {})
 
 -----------分割線
// 分解一下,也就是下面這樣
let executor = (resolve, reject) => {
    resolve('hello'); // or reject('hello')
}

 new Promise(executor)
 .then(res => {})
 .catch(err => {})
複製代碼

咱們來分析一下他有哪些功能/特性瀏覽器

  • 一、構造函數裏傳一個函數的兩個參數(resolve, reject)
  • 二、resolve 成功時執行的回調
  • 三、reject 失敗時執行的回調
  • 四、三種狀態
    • pending [待定] 初始狀態
    • fulfilled [實現] 操做成功
    • rejected [被否決] 操做失敗
  • 五、Promise 對象方法 then
  • 六、異步實現
  • 七、onFulfilled 和 onRejected 的異步調用
  • 八、值穿透
  • 九、Promise 對象方法 catch
  • 十、Promise 對象方法 all
  • 十一、Promise 對象方法 race
  • 十二、Promise 對象方法 resolve
  • 1三、Promise 對象方法 reject
  • 1三、Promise 對象方法 allSettled(上個月 TC39 出來的新特性)

接下來,咱們要一一撕下他的假裝,揭開他的真面目。bash

Promise 的基本結構實現

基於上面分析結果,咱們先來實現前面三個功能:markdown

  • 一、構造函數裏傳一個函數的兩個參數(resolve, reject)
  • 二、resolve 成功時執行的回調
  • 三、reject 失敗時執行的回調
class Promise {
    constructor(executor) {
        // 定義 resolve
        let resolve = res => {}
        // 定義 reject
        let reject = err => {}

        // 自動執行
        executor(resolve, reject);
    }
}

// 測試一下:
new Promise((resolve, reject) => {
    console.log('執行到啦~')
})
複製代碼

能夠將上面代碼複製到控制檯執行,查看效果:異步

Promise 三種狀態實現

Ok,fine,接下來,咱們來實現她的三種狀態。函數

  • 四、三種狀態
    • pending [待定] 初始狀態
    • fulfilled [實現] 操做成功
    • rejected [被否決] 操做失敗

promise 狀態有以下特色:
1.promise 對象初始化狀態爲 pendingoop

2.當調用resolve(成功),會由pending => fulfilled測試

3.當調用reject(失敗),會由pending => rejected

Promsie 狀態 只能由 pending => fulfilled/rejected, 一旦修改就不能再變

class Promise {
    constructor(executor) {
        this.status = "pending"; // 默認狀態
        this.value;  // resolve 成功時的值
        this.error;  // reject 失敗時的值

        let resolve = res => {
            if(this.status === "pending") {
                this.value = res;
                this.status = "resolved";
            }
        }

        let reject = err => {
            if(this.status === "pending") {
                this.error = err;
                this.status = "rejected";
            }
        }

        executor(resolve, reject);
    }
}
複製代碼

1)pending [待定] 初始狀態

測試一下,若是不去resolve,也不去reject

// 測試一下:
new Promise((resolve, reject) => {
    
})
複製代碼

那麼Promise應該是初始狀態。咱們將上面代碼執行測試一下,獲得結果以下:

此時狀態是: {status: "pending"}

2)fulfilled [實現] 操做成功

當咱們執行 resolve

// 測試一下:
new Promise((resolve, reject) => {
   resolve('成功啦~'); 
})
複製代碼

將獲得結果以下:

3)rejected [被否決] 操做失敗

當執行 reject

// 測試一下:
new Promise((resolve, reject) => {
    resolve('失敗啦~')
})
複製代碼

Promise 對象方法 then 實現

  • 五、Promise 對象方法 then

Promise 這個對象有 then 方法,仍是先來分析,then 有什麼?

then 接受兩個回調

promise.then(onFulfilled, onRejected); // 這裏假設 promise 繼承於 Promise 類
複製代碼

咱們繼續在前面 Promise 類中書寫 then 方法:

class Promise {
        constructor(executor) {
            this.status = "pending"; // 默認promise狀態
            this.value;  // resolve成功時的值
            this.error;  // reject失敗時的值

            let resolve = res => {
                if(this.status === "pending") {
                    this.value = res;
                    this.status = "resolved";
                }
            }

            let reject = err => {
                if(this.status === "pending") {
                    this.error = err;
                    this.status = "rejected";
                }
            }

            executor(resolve, reject)
        }

        // 聲明 then
        then(onFullfilled, onRejected) {
            if(this.status === "resolved") {
                onFullfilled(this.value)
            }
            if(this.status === "rejected") {
                onRejected(this.error)
            }
        }
    }
複製代碼

測試一下:

new Promise((resolve, reject) => {
        resolve("成功啦~"); // 或  reject("失敗啦~")
    })
    .then(res => {
        console.log(res);
    }, err => {
        console.log(err);
    })
複製代碼

獲得結果:

異步實現

  • 六、異步實現

至此,基本實現簡單的同步代碼,可是當 resolve 在 setTimeout 內執行,then 時 state 仍是 pending 等待狀態。咱們就須要在 then 調用的時候,將成功和失敗存到各自的數組,一旦 reject 或者 resolve,就調用它們。

相似於分佈訂閱,先將 then 內的兩個函數存儲,因爲 promise 能夠有多個 then,因此存在同一個數組內。當成功或失敗的時候用 forEach 調用他們。

class Promise {
        constructor(executor) {
            this.status = "pending"; // 默認promise狀態
            this.value;  // resolve成功時的值
            this.error;  // reject失敗時的值
+           this.resolveQueue = []; // 成功存放的數組
+           this.rejectQueue = []; // 失敗存放法數組

            let resolve = value => {
                if(this.status === "pending") {
                    this.value = value;
                    this.status = "resolved";
                    // 一旦resolve執行,調用成功數組的函數
+                   this.resolveQueue.forEach(fn => fn());
                }
            }

            let reject = value => {
                if(this.status === "pending") {
                    this.error = value;
                    this.status = "rejected";
                }
                // 一旦reject執行,調用失敗數組的函數
+               this.rejectQueue.forEach(fn => fn());
            }

            executor(resolve, reject)
        }
        
        // 執行到then的時候
        then(onFullfilled, onRejected) {
            if(this.status === "resolved") {
                this.resolveQueue.push(() => {
                    onFullfilled(this.value);
                })
            }
            if(this.status === "rejected") {
                this.rejectQueue.push(() => {
                    onRejected(this.error);
                })
            }
            // 當狀態state爲pending時
+           if(this.status === "pending") {
                // onFulfilled傳入到成功數組
+               this.resolveQueue.push(() => {
+                   onFullfilled(this.value);
+              })
                // onRejected傳入到失敗數組
+               this.rejectQueue.push(() => {
+                   onRejected(this.error);
+               })
+           }
        }
    }
複製代碼

then 的鏈式調用

  • 七、then 的鏈式調用

咱們經常用到new Promise().then().then()這樣的寫法,這就是鏈式調用,原來是用於解決地獄回調的。那麼如何去實現呢? 爲了達到這個效果,咱們能夠再第一個 then 函數內再返回一個 Promise,讓這個新的 Promise 返回的值傳遞到下一個 then 中。

一句話總結:

經過在 then 中 return 一個新的 Promise,從而實現 then 的鏈式調用!

代碼以下:

class Promise {
    constructor(executor) {
        this.status = "pending"; // 默認promise狀態
        this.value;  // resolve 成功時的值
        this.error;  // reject 失敗時的值
        this.resolveQueue = []; // 成功時回調隊列
        this.rejectQueue = []; // 失敗時回調隊列

        let resolve = value => {
            if(this.status === "pending") {
                this.value = value;
                this.status = "resolved";
                this.resolveQueue.forEach(fn => fn())
            }
        }

        let reject = value => {
            if(this.status === "pending") {
                this.error = value;
                this.status = "rejected";
                this.rejectQueue.forEach(fn => fn())
            }
        }

        executor(resolve, reject)
    }

    then(onFullfilled, onRejected) {
        let promise2;
        if(this.status === "resolved") {
            promise2 = new Promise((resolve, reject) => {
                let x = onFullfilled(this.value);
                resolvePromise(promise2, x, resolve, reject);
            })
        }
        if(this.status === "rejected") {
            promise2 = new Promise((resolve, reject) => {
                let x = onRejected(this.value);
                resolvePromise(promise2, x, resolve, reject);
            })
        }
        if(this.status === "pending") {
            promise2 = new Promise((resolve, reject) => {
                this.resolveQueue.push(() => {
                    let x = onFullfilled(this.value);
                    resolvePromise(promise2, x, resolve, reject);
                })
                this.rejectQueue.push(() => {
                    let x = onRejected(this.error);
                    resolvePromise(promise2, x, resolve, reject);
                })
            })
        }

        return promise2;
    }
}

-------------------分割線
// 將上面代碼整理一下
class Promise {
    constructor(executor) {
        this.status = "pending"; // 默認promise狀態
        this.value;  // resolve成功時的值
        this.error;  // reject失敗時的值
        this.resolveQueue = []; // 成功時回調隊列
        this.rejectQueue = []; // 失敗時回調隊列

        let resolve = value => {
            if(this.status === "pending") {
                this.value = value;
                this.status = "resolved";
                this.resolveQueue.forEach(fn => fn())
            }
        }

        let reject = value => {
            if(this.status === "pending") {
                this.error = value;
                this.status = "rejected";
                this.rejectQueue.forEach(fn => fn())
            }
        }

        executor(resolve, reject)
    }

    then(onFullfilled, onRejected) {
        let promise2;
        promise2 = new Promise((resolve, reject) => {
            if(this.status === "resolved") {
                let x = onFullfilled(this.value);
                // resolvePromise函數,處理本身return的promise和默認的promise2的關係
                resolvePromise(promise2, x, resolve, reject);
            }
            if(this.status === "rejected") {
                let x = onRejected(this.value);
                resolvePromise(promise2, x, resolve, reject);
            }
            if(this.status === "pending") {
                this.resolveQueue.push(() => {
                    let x = onFullfilled(this.value);
                    resolvePromise(promise2, x, resolve, reject);
                })
                this.rejectQueue.push(() => {
                    let x = onRejected(this.error);
                    resolvePromise(promise2, x, resolve, reject);
                })
            }
        });
        
        // 返回 promise,達成鏈式效果
        return promise2;
    }
}
複製代碼

最後,咱們來完成上面的 resolvePromise 函數,咱們暫且將第一個 then 返回的值成爲 x,在這個函數中,咱們須要去判斷 x 是否是 promise(這裏是重點!):

  • 是:則取他的結果,做爲新的 promise2 成功的結果
  • 不是:直接做爲新的 promise2 成功的結果

resolvePromise 代碼以下:

/**
 * 處理promise遞歸的函數
 *
 * promise2 {Promise} 默認返回的promise
 * x {*} 咱們本身 return 的對象
 * resolve
 * reject
 */
 function resolvePromise(promise2, x, resolve, reject){
  
  // 循環引用報錯
  if(x === promise2){
    // reject 報錯拋出
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  
  // 鎖,防止屢次調用
  let called;
  
  // x 不是 null 且 x 是對象或者函數
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+ 規定,聲明then = x的then方法
      let then = x.then;
      
      // 若是then是函數,就默認是promise了
      if (typeof then === 'function') { 
        // then 執行 第一個參數是 this 後面是成功的回調 和 失敗的回調
        then.call(x, y => {
          // 成功和失敗只能調用一個
          if (called) return;
          called = true;
          
          // 核心點2:resolve 的結果依舊是 promise 那就繼續遞歸執行
          // 核心點2:resolve 的結果依舊是 promise 那就繼續遞歸執行
          // 核心點2:resolve 的結果依舊是 promise 那就繼續遞歸執行
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          // 成功和失敗只能調用一個
          if (called) return;
          called = true;
          reject(err);// 失敗了就失敗了
        })
      } else {
        resolve(x); // 直接成功便可
      }
    } catch (e) { // 走到 catch 也屬於失敗
      if (called) return;
      called = true;
      // 取then出錯了那就不要在繼續執行了
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
複製代碼

then 鏈式調用測試

完整測試代碼以下,能夠複製進瀏覽器控制檯執行下:

function resolvePromise(promise2, x, resolve, reject){
    // 循環引用報錯
    if(x === promise2){
      // reject 報錯拋出
      return reject(new TypeError('Chaining cycle detected for promise'));
    }
    // 鎖,防止屢次調用
    let called;
    
    // x不是null 且x是對象或者函數
    if (x != null && (typeof x === 'object' || typeof x === 'function')) {
      try {
        // A+ 規定,聲明then = x的then方法
        let then = x.then;
        // 若是then是函數,就默認是promise了
        if (typeof then === 'function') { 
          // 就讓then執行 第一個參數是this   後面是成功的回調 和 失敗的回調
          then.call(x, y => {
            // 成功和失敗只能調用一個
            if (called) return;
            called = true;
            // resolve的結果依舊是promise 那就繼續遞歸執行
            resolvePromise(promise2, y, resolve, reject);
          }, err => {
            // 成功和失敗只能調用一個
            if (called) return;
            called = true;
            reject(err);// 失敗了就失敗了
          })
        } else {
          resolve(x); // 直接成功便可
        }
      } catch (e) {
        // 也屬於失敗
        if (called) return;
        called = true;
        // 取then出錯了那就不要在繼續執行了
        reject(e); 
      }
    } else {
      resolve(x);
    }
  }


class Promise {
    constructor(executor) {
        this.status = "pending"; // 默認promise狀態
        this.value;  // resolve成功時的值
        this.error;  // reject失敗時的值
        this.resolveQueue = []; // 成功時回調隊列
        this.rejectQueue = []; // 失敗時回調隊列

        let resolve = value => {
            if(this.status === "pending") {
                this.value = value;
                this.status = "resolved";
                this.resolveQueue.forEach(fn => fn())
            }
        }

        let reject = value => {
            if(this.status === "pending") {
                this.error = value;
                this.status = "rejected";
                this.rejectQueue.forEach(fn => fn())
            }
        }

        executor(resolve, reject)
    }

    then(onFullfilled, onRejected) {
        let promise2;
        promise2 = new Promise((resolve, reject) => {
            if(this.status === "resolved") {
                let x = onFullfilled(this.value);
                // resolvePromise函數,處理本身return的promise和默認的promise2的關係
                resolvePromise(promise2, x, resolve, reject);
            }
            if(this.status === "rejected") {
                let x = onRejected(this.value);
                resolvePromise(promise2, x, resolve, reject);
            }
            if(this.status === "pending") {
                this.resolveQueue.push(() => {
                    let x = onFullfilled(this.value);
                    resolvePromise(promise2, x, resolve, reject);
                })
                this.rejectQueue.push(() => {
                    let x = onRejected(this.error);
                    resolvePromise(promise2, x, resolve, reject);
                })
            }
        });
        
        // 返回 promise,達成鏈式效果
        return promise2;
    }
}

// 測試如下代碼
new Promise((resolve, reject) => {
    resolve();
}).then((res)=>{
    console.log('進入第一個then!')
    return new Promise((resolve,reject)=>{
        resolve('hello world');
    })
}).then((res)=>{
    console.log('進入第二個then!', res);
})
複製代碼

ok,咱們實現了 then 的鏈式調用,這也是實現 Promise 中的重難點!

onFulfilled 和 onRejected 的異步調用

  • 八、onFulfilled 和 onRejected 的異步調用

核心思路:

用setTimeout解決異步問題

代碼以下:

class Promise {
    constructor(executor) {
        this.status = "pending"; // 默認promise狀態
        this.value;  // resolve成功時的值
        this.error;  // reject失敗時的值
        this.resolveQueue = []; // 成功時回調隊列
        this.rejectQueue = []; // 失敗時回調隊列

        let resolve = value => {
            if(this.status === "pending") {
                this.value = value;
                this.status = "resolved";
                this.resolveQueue.forEach(fn => fn())
            }
        }

        let reject = value => {
            if(this.status === "pending") {
                this.error = value;
                this.status = "rejected";
                this.rejectQueue.forEach(fn => fn())
            }
        }

        executor(resolve, reject)
    }

    then(onFullfilled, onRejected) {
        let promise2;
        promise2 = new Promise((resolve, reject) => {
            if(this.status === "resolved") {
                // 異步
+               setTimeout(() => {
                    let x = onFullfilled(this.value);
                    // resolvePromise函數,處理本身return的promise和默認的promise2的關係
                    resolvePromise(promise2, x, resolve, reject);
+               }, 0)
            }
            if(this.status === "rejected") {
                // 異步
+               setTimeout(() => {
                    let x = onRejected(this.value);
                    resolvePromise(promise2, x, resolve, reject);
+               }, 0)
            }
            if(this.status === "pending") {
                this.resolveQueue.push(() => {
                    // 異步
+                   setTimeout(() => {
                        let x = onFullfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
+                   }, 0)
                })
                this.rejectQueue.push(() => {
                    // 異步
+                   setTimeout(() => {
                        let x = onRejected(this.error);
                        resolvePromise(promise2, x, resolve, reject);
+                   }, 0)
                })
            }
        });
        
        // 返回 promise,達成鏈式效果
        return promise2;
    }
}
複製代碼

值穿透調用

  • 九、值穿透
new Promise((resolve, reject)=>{
    resolve('YoYo');
}).then().then().then().then().then().then().then((res)=>{ 
    console.log(res);
})
複製代碼

當執行上面多個 then,咱們指望最後那個 then 打印出 'YoYo'。

實現很簡單:onFulfilled 若是不是函數,就忽略 onFulfilled,直接返回 value!

相應的,咱們也要處理下沒有 onRejected 的狀況:onRejected 若是不是函數,就忽略 onRejected,直接扔出錯誤!

代碼以下,在以前的 Promise 類的 then 加入:

then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err;}
    
    <!--... 省略-->
}
複製代碼

Promise 對象方法 catch

  • 十、Promise 對象方法 catch

核心思路:

catch 是失敗的回調,至關於執行 this.then(null,fn)

class Promise {
        constructor(executor) {
           <!--... 省略-->
        }

        then(onFullfilled, onRejected) {
            <!--... 省略-->
        }
        
+       catch(onRejected) {
+           return this.then(null, onRejected)
+       }
    }
複製代碼

此外,咱們還須要對其餘幾個函數外使用 try/catch 去作異常捕獲,這裏不展開,理解便可(本文最後源碼中會展現)。

Promise 對象方法 all

  • 十、Promise 對象方法 all

這是一道經典面試題!

Promise.all() 接收一個數組做爲參數,該方法返回一個 Promise 實例,此實例在 iterable 參數內全部的 promise 都「完成(resolved)」或參數中不包含 promise 時回調完成(resolve);若是參數中 promise 有一個失敗(rejected),此實例回調失敗(reject),失敗的緣由是第一個失敗 promise 的結果。

用法以下:

var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
}); 

Promise.all([p1, p2, p3]).then(values => { 
  console.log(values); // [3, 1337, "foo"] 
});
複製代碼

接下來看看如何實現: 下面手寫的,沒有測試,回頭補充!下班先~

Promise.all = function(promises) {
    let count = 0;
    let res = [];
    return new Promise((resolve, reject) => {
        for(let i = 0; i<promises.length; i++) {
            promises[i].then(res => {
                res.push(res);
                count++;
                if(count === promises.length) resolve(res);
            })
        }
    })
    .catch(err => {
        reject(err);
    })
}
複製代碼

Promise 對象方法 race

  • 十一、Promise 對象方法 race

Promise.race() 它一樣接收一個promise對象組成的數組做爲參數,並返回一個新的promise對象。一旦迭代器中的某個promise解決或拒絕,返回的 promise就會解決或拒絕。

Promise.race = function(promises) {
    return new Promise((resolve, reject) => {
        for(let i = 0; i<promises.length; i++) {
            promises[i].then(resolve, reject);
        }
    })
}
複製代碼

Promise 對象方法 resolve

  • 十二、Promise 對象方法 resolve
Promise.resolve = function(value) {
    return new Promise((resolve, reject) => {
        resolve(value);
    })
}
複製代碼

Promise 對象方法 reject

  • 1三、Promise 對象方法 reject
Promise.reject = function(value) {
    return new Promise((resolve, reject) => {
        reject(value);
    })
}
複製代碼

Promise 對象方法 allSettled

當課後做業吧,同窗們本身寫,能夠放回復中哈~

Reference: Promises/A+

相關文章
相關標籤/搜索