手寫promise 淺析簡單的promise源碼的實現

 

Promise 對象用於表示一個異步操作的最終完成 (或失敗), 及其結果值.它是用於解決回調地獄的尷尬與醜陋的神器。

這裏附上MDN地址:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

可以看下具體的用法。但是今天我們試着手寫一個簡單(簡陋)的promise,來進一步認識她是如何處理異步操作的。

就像汽車行業的逆向研發一樣,我們也是根據 promise使用的API,來倒推出promise源碼,這樣會比較好理解。

 

1、promise的用法

想要寫源碼,我們其實根據其API倒退我們準備寫的源碼。畢竟我們現在是在逆向開發。所以看看我們用常用的promise用法。先看一下最常用的以下兩種場景:

//場景一:直接調用 -> 這就意味着在寫源碼的時候 Promise方法上本身綁定的就有 resolve 或者 reject的屬性

Promise.resolve(1000); // -> 成功的值
Promise.reject('some error'); // -> 失敗的值

//場景二:實例化 -> 這就意味着原型方法上要有個 then的方法
new Promise((resolve, reject) => {
    // 這裏來個異步的代碼
    settimeout(() => {
        resolve(1000);
    },1000)

}).then(res => {
    console.log('接受的結果', res)
})

2、Promise 構造函數的‘逆向研發’

我們先來打一個基本的架子 - 構造函數。

// 毫無疑問, Promise是一個構造函數。
// 直接上構造函數的基本配置:大寫函數,原型方法

function Promise () {
    



}

// 上邊我們說了,實例方法 有個 then 的方法用來接收結果的,那麼 這個方法定義在 prototype上更合適

Promise.prototype.then = function () {
    // 這裏接收結果    

}

架子好了之後,我們要考慮這構造函數中都有些設麼東西。

A、狀態 status

我們知道promise內部的三大狀態: pending(等待),fulfilled(成功), rejected(失敗)。promise對象的狀態,從_Pending_轉換爲_Fulfilled_或_Rejected_之後, 這個promise對象的狀態就不會再發生任何變化。如下圖:

那麼構造函數理應有個 狀態(status)的屬性;

function Promise () {

    // 默認的初始狀態
    this.status = 'pending';
}

B、值(錯誤) value或error

有了狀態 之後,要有對應的值。

function Promise () {

    // 狀態
    this.status = 'pending';
    
    // 成功的值 對應的是resolve ,默認爲 null
    this.value = null;
    // 失敗的原因 對應的是reject,默認爲null
    this.reason = null;



}

 C、構造函數傳參 executor  

executor是帶有 resolve 和 reject 兩個參數的函數 。

說完了上邊的值和錯誤,肯定是有兩個函數分別用來處理對應的值: resolve(處理成功的值 - value),reject(處理錯誤的信息 - error)。注意,這兩個參數都是函數。

// executor -> 是一個函數 函數包含了resolve, reject兩個函數;
// 我們用promise的時候也是這樣的 new Promise((resolve, reject) => {resolve(1000)})

function Promise (executor ) {

    
    if (typeof executor != 'function') {
       throw new Error(`${executor} is not a function`)
    }

    this.status = 'PENDING';// 狀態
    this.value = null; // 成功 resolve 的值
    this.reason = null;// 失敗 reject 的值

    this.resolve = (value) => {
    // 確保狀態是 pending才能 更新狀態
       if (this.status == 'pending') {
           this.status = 'fulfilled';
           this.value = value;
       }

   }

   this.reject = (reason) => {
    // 確保狀態是 pending才能 更新狀態
    if (this.status == 'pending') {
       this.status = 'rejected';
       this.reason = reason;
    }
  }
    
  // 執行這個傳遞進來的函數
  executor(this.resolve, this.reject);
}

D、then 方法

不論是成功或是失敗都會調用then方法,用來接收傳遞過來的值。

Promise.prototype.then = function (onFulFilled, onRejected) {

    // -> 成功時傳值的處理
    if (this.status == 'fulfilled') {
        if (typeof onFulFilled == 'function') {
          onFulFilled(this.value)
        }
    }
    
    // -> 失敗時傳值的處理
    if (this.status == 'rejected') {
       if (typeof onRejected == 'function') {
         onRejected(this.reason)    
       }
    }
}

好了,現在主體的方法基本處理完了。現在我們來試驗一下,我們寫的這個promise

// 場景一: -> 用來處理 同步的代碼
new Promise((resolve, reject) => {
    console.log('promise 執行了')  // -> 輸出成功,同步代碼通過了
})


// 但是我們一開始就說,promise的用處在於處理異步操作。
// 場景二: -> 用來處理 異步代碼
new Promise((resolve, reject) => {
    settimeout(() => {
        // 異步代碼操作 以成功傳值爲例
        resolve(1000)
    },1000)
}).then(res => console.log(res))
// -> 結果是 沒有任何輸出結果。

處理異步的時候,我們目前的代碼並不能執行。爲什麼呢?我們要注意,異步代碼並不會立即執行。而是在主體代碼執行之後,纔會來處理異步代碼。所以,在一開始執行then方法的時候,promise的狀態保持在 pending。那麼既然pending,因此then方法即沒有調用onFulfilled也沒有調用onRejected。所以這個值根本就不會被捕獲到。

那麼如何處理異步代碼?

F、處理異步代碼

上邊說到,then執行的時候 promise的狀態始終在 pending. 所以,並不能處理回調。那麼我們在他 pending的時候用一個數組,把它存起來,在 resolve 的時候執行就可以了。

function promise (executor) {
    this.status = 'pending';

    this.value = null;
    this.reason = null;
    // 在這裏加上兩個暫存數組 分別存儲 resolve的異步函數和reject的異步函數
    this.onFulFilledAry = [];
    this.onRejectedAry = [];

    this.resolve = value = > {
          if (this.status == 'pending') {
             this.value = value;
             this.status = 'fulfilled'  
             // 在這裏執行所有resolve暫存的回調
             this.onFulFilledAry.forEach(fn => fn(value)) 
          }
    }

    this.reject = reason => {
        if (this.status == 'pending') {
            this.status = 'rejected';
            // 在這裏執行所有的reject的暫存回調
            this.onRejectedAry.forEach(fn => fn(reason))
            this.reason = reason;
        }
    }

    executor(this.resolve, this.reject);
}

Promise.prototype.then = function (onFulFilled, onRejected) {

   // 這裏主要處理異步代碼的執行
    
    if (this.status == 'pending') {
        if (typeof onFulFilled == 'function) {
            // 成功的回調函數存進暫存的數組中
            this.onFulFilledAry.push(onFulFilled )
        }

        if (typeof onRejected== 'function) {
            // 失敗的回調函數存進暫存的數組中
            this.onRejectedAry.push(onRejected)
        }
    }
    
    
  // 下邊的resolve和reject的狀態的代碼不寫了
  ......
}

我們再次測試一下,異步的代碼。

let p = new Promise((resolve, reject) => {
       settimeout(() => {
            resolve(1000)
       }, 1000)
})
 
p.then(res => console.log(res)) // -> 成功輸出 1000

至此,Promise已經支持了異步操作,promise最基本的功能已經基本實現了。

至於還有其他的API,比如 promise.all promise.race等等。後續有時間繼續探究分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章