30分鐘,帶你實現一個符合規範的 Promise(巨詳細)

前言

關於 Promise 原理解析的優秀文章,在掘金上已經有很是多了。可是筆者老是處在 看了就會,一寫就廢 的狀態,這是筆者寫這篇文章的目的,爲了理一下 Promise 的編寫思路,從零開始手寫一波代碼,同時也方便本身往後回顧。javascript

 

Promise 的做用

PromiseJavaScript 異步編程的一種流行解決方案,它的出現是爲了解決 回調地獄 的問題,讓使用者能夠經過鏈式的寫法去編寫寫異步代碼,具體的用法筆者就不介紹了,你們能夠參考阮一峯老師的 ES6 Promise教程java

 

課前知識

觀察者模式

什麼是觀察者模式:git

觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個目標對象,當這個目標對象的狀態發生變化時,會通知全部觀察者對象,使它們可以自動更新。

Promise 是基於 觀察者的設計模式 實現的,then 函數要執行的函數會被塞入觀察者數組中,當 Promise 狀態變化的時候,就去執行觀察組數組中的全部函數。es6

事件循環機制

實現 Promise 涉及到了 JavaScript 中的事件循環機制 EventLoop、以及宏任務和微任務的概念。github

事件循環機制的流程圖以下:面試

你們能夠看一下這段代碼:typescript

console.log(1);

setTimeout(() => {
  console.log(2);
},0);

let a = new Promise((resolve) => {
  console.log(3);
  resolve();
}).then(() => {
  console.log(4);
}).then(() => {
  console.log(5);
});

console.log(6);

若是不能一會兒說出輸出結果,建議你們能夠先查閱一下 事件循環 的相關資料,在掘金中有不少優秀的文章。shell

Promises/A+ 規範

Promises/A+ 是一個社區規範,若是你想寫出一個規範的 Promise,咱們就須要遵循這個標準。以後咱們也會根據規範來完善咱們本身編寫的 Promisenpm

 

Promise 核心知識點

在動手寫 Promise 以前,咱們先過一下幾個重要的知識點。編程

executor

// 建立 Promise 對象 x1
// 並在 executor 函數中執行業務邏輯
function executor(resolve, reject){
  // 業務邏輯處理成功結果
  const value = ...;
  resolve(value);
  // 失敗結果
  // const reason = ...;
  // reject(reason);
}

let x1 = new Promise(executor);

首先 Promise 是一個類,它接收一個執行函數 executor,它接收兩個參數:resolvereject,這兩個參數是 Promise 內部定義的兩個函數,用來改變狀態並執行對應回調函數。

由於 Promise 自己是不知道執行結果失敗或者成功,它只是給異步操做提供了一個容器,實際上的控制權在使用者的手上,使用者能夠調用上面兩個參數告訴 Promise 結果是否成功,同時將業務邏輯處理結果( value/reason)做爲參數傳給 resolvereject 兩個函數,執行回調。

三個狀態

Promise 有三個狀態:

  • pending:等待中
  • resolved:已成功
  • rejected:已失敗

Promise 的狀態改變只有兩種可能:從 pending 變爲 resolved 或者從 pending 變爲 rejected,以下圖(引自 Promise 迷你書):

引自 Promise 迷你書

並且須要注意的是一旦狀態改變,狀態不會再變了,接下來就一直是這個結果。也就是說當咱們在 executor 函數中調用了 resolve 以後,以後調用 reject 就沒有效果了,反之亦然。

// 並在 executor 函數中執行業務邏輯
function executor(resolve, reject){
  resolve(100);
  // 以後調用 resolve,reject 都是無效的,
  // 由於狀態已經變爲 resolved,不會再改變了
  reject(100);
}

let x1 = new Promise(executor);

then

每個 promise 都一個 then 方法,這個是當 promise 返回結果以後,須要執行的回調函數,他有兩個可選參數:

  • onFulfilled:成功的回調;
  • onRejected:失敗的回調;

以下圖(引自 Promise 迷你書):

引自 Promise 迷你書

// ...
let x1 = new Promise(executor);

// x1 延遲綁定回調函數 onResolve
function onResolved(value){
  console.log(value);
}

// x1 延遲綁定回調函數 onRejected
function onRejected(reason){
  console.log(reason);
}

x1.then(onResolved, onRejected);

 

手寫 Promise 大體流程

在這裏咱們簡單過一下手寫一個 Promise 的大體流程:

executor 與三個狀態

  • new Promise 時,須要傳遞一個 executor 執行器函數,在構造函數中,執行器函數馬上執行
  • executor 執行函數接受兩個參數,分別是 resolvereject
  • Promise 只能從 pendingrejected, 或者從 pendingfulfilled
  • Promise 的狀態一旦確認,狀態就凝固了,不在改變

then 方法

  • 全部的 Promise 都有 then 方法,then 接收兩個參數,分別是 Promise 成功的回調 onFulfilled,和失敗的回調 onRejected
  • 若是調用 then 時,Promise 已經成功,則執行 onFulfilled,並將 Promise 的值做爲參數傳遞進去;若是 Promise 已經失敗,那麼執行 onRejected,並將 Promise 失敗的緣由做爲參數傳遞進去;若是 Promise 的狀態是 pending,須要將 onFulfilledonRejected 函數存放起來,等待狀態肯定後,再依次將對應的函數執行(觀察者模式)
  • then 的參數 onFulfilledonRejected 能夠不傳,Promise 能夠進行值穿透

鏈式調用並處理 then 返回值

  • Promise 能夠 then 屢次,Promisethen 方法返回一個新的 Promise
  • 若是 then 返回的是一個正常值,那麼就會把這個結果(value)做爲參數,傳遞給下一個 then 的成功的回調(onFulfilled
  • 若是 then 中拋出了異常,那麼就會把這個異常(reason)做爲參數,傳遞給下一個 then 的失敗的回調(onRejected)
  • 若是 then 返回的是一個 promise 或者其餘 thenable 對象,那麼須要等這個 promise 執行完撐,promise 若是成功,就走下一個 then 的成功回調;若是失敗,就走下一個 then 的失敗回調。

上面是大體的實現流程,若是迷迷糊糊不要緊,只要大體有一個印象便可,後續咱們會一一講到。

那接下來咱們就開始實現一個最簡單的例子開始講解。

 

初版(從一個簡單例子開始)

咱們先寫一個簡單版,這版暫不支持狀態、鏈式調用,而且只支持調用一個 then 方法。

來個 🌰

let p1 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
      resolved('成功了');
    }, 1000);
})

p1.then((data) => {
    console.log(data);
}, (err) => {
    console.log(err);
})

例子很簡單,就是 1s 以後返回 成功了,並在 then 中輸出。

實現

咱們定義一個 MyPromise 類,接着咱們在其中編寫代碼,具體代碼以下:

class MyPromise {
  // ts 接口定義 ...
  constructor (executor: executor) {
    // 用於保存 resolve 的值
    this.value = null;
    // 用於保存 reject 的值
    this.reason = null;
    // 用於保存 then 的成功回調
    this.onFulfilled = null;
    // 用於保存 then 的失敗回調
    this.onRejected = null;

    // executor 的 resolve 參數
    // 用於改變狀態 並執行 then 中的成功回調
    let resolve = value => {
      this.value = value;
      this.onFulfilled && this.onFulfilled(this.value);
    }

    // executor 的 reject 參數
    // 用於改變狀態 並執行 then 中的失敗回調
    let reject = reason => {
      this.reason = reason;
      this.onRejected && this.onRejected(this.reason);
    }

    // 執行 executor 函數
    // 將咱們上面定義的兩個函數做爲參數 傳入
    // 有可能在 執行 executor 函數的時候會出錯,因此須要 try catch 一下 
    try {
      executor(resolve, reject);
    } catch(err) {
      reject(err);
    }
  }

  // 定義 then 函數
  // 而且將 then 中的參數複製給 this.onFulfilled 和 this.onRejected
  private then(onFulfilled, onRejected) {
    this.onFulfilled = onFulfilled;
    this.onRejected = onRejected;
  }
}

好了,咱們的初版就完成了,是否是很簡單。

不過這裏須要注意的是, resolve 函數的執行時機須要在 then 方法將回調函數註冊了以後,在 resolve 以後在去往賦值回調函數,其實已經完了,沒有任何意義。

上面的例子沒有問題,是由於 resolve(成功了) 是包在 setTimeout 中的,他會在下一個宏任務執行,這時回調函數已經註冊了。

你們能夠試試把 resolve(成功了)setTimeout 中拿出來,這個時候就會出現問題了。

存在問題

這一版實現很簡單,還存在幾個問題:

  • 未引入狀態的概念

未引入狀態的概念,如今狀態能夠隨意變,不符合 Promise 狀態只能從等待態變化的規則。

  • 不支持鏈式調用

正常狀況下咱們能夠對 Promise 進行鏈式調用:

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolved('成功了');
  }, 1000);
})

p1.then(onResolved1, onRejected1).then(onResolved2, onRejected2)
  • 只支持一個回調函數,若是存在多個回調函數的話,後面的會覆蓋前面的

在這個例子中,onResolved2 會覆蓋 onResolved1onRejected2 會覆蓋 onRejected1

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolved('成功了');
  }, 1000);
})

// 註冊多個回調函數
p1.then(onResolved1, onRejected1);
p1.then(onResolved2, onRejected2);

接下來咱們更進一步,把這些問題給解決掉。

 

第二版(實現鏈式調用)

這一版咱們把狀態的概念引入,同時實現鏈式調用的功能。

加上狀態

上面咱們說到 Promise 有三個狀態:pendingresovledrejected,只能從 pending 轉爲 resovled 或者 rejected,並且當狀態改變以後,狀態就不能再改變了。

  • 咱們定義一個屬性 status:用於記錄當前 Promise 的狀態
  • 爲了防止寫錯,咱們把狀態定義成常量 PENDINGRESOLVEDREJECTED
  • 同時咱們將保存 then 的成功回調定義爲一個數組:this.resolvedQueuesthis.rejectedQueues,咱們能夠把 then 中的回調函數都塞入對應的數組中,這樣就能解決咱們上面提到的第三個問題。
class MyPromise {
  private static PENDING = 'pending';
  private static RESOLVED = 'resolved';
  private static REJECTED = 'rejected';

  constructor (executor: executor) {
    this.status = MyPromise.PENDING;
    // ...

    // 用於保存 then 的成功回調數組
    this.resolvedQueues = [];
    // 用於保存 then 的失敗回調數組
    this.rejectedQueues = [];

    let resolve = value => {
      // 當狀態是 pending 是,將 promise 的狀態改成成功態
      // 同時遍歷執行 成功回調數組中的函數,將 value 傳入
      if (this.status == MyPromise.PENDING) {
        this.value = value;
        this.status = MyPromise.RESOLVED;
        this.resolvedQueues.forEach(cb => cb(this.value))
      }
    }

    let reject = reason => {
      // 當狀態是 pending 是,將 promise 的狀態改成失敗態
      // 同時遍歷執行 失敗回調數組中的函數,將 reason 傳入
      if (this.status == MyPromise.PENDING) {
        this.reason = reason;
        this.status = MyPromise.REJECTED;
        this.rejectedQueues.forEach(cb => cb(this.reason))
      }
    }

    try {
      executor(resolve, reject);
    } catch(err) {
      reject(err);
    }
  }
}

完善 then 函數

接着咱們來完善 then 中的方法,以前咱們是直接將 then 的兩個參數 onFulfilledonRejected,直接賦值給了 Promise 的用於保存成功、失敗函數回調的實例屬性。

如今咱們須要將這兩個屬性塞入到兩個數組中去:resolvedQueuesrejectedQueues

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    // 首先判斷兩個參數是否爲函數類型,由於這兩個參數是可選參數
    // 當參數不是函數類型時,須要建立一個函數賦值給對應的參數
    // 這也就實現了 透傳
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason}

    // 當狀態是等待態的時候,須要將兩個參數塞入到對應的回調數組中
    // 當狀態改變以後,在執行回調函數中的函數
    if (this.status === MyPromise.PENDING) {
      this.resolvedQueues.push(onFulfilled)
      this.rejectedQueues.push(onRejected)
    }

    // 狀態是成功態,直接就調用 onFulfilled 函數
    if (this.status === MyPromise.RESOLVED) {
      onFulfilled(this.value)
    }

    // 狀態是成功態,直接就調用 onRejected 函數
    if (this.status === MyPromise.REJECTED) {
      onRejected(this.reason)
    }
  }
}

then 函數的一些說明

  • 什麼狀況下 this.status 會是 pending 狀態,什麼狀況下會是 resolved 狀態

這個其實也和事件循環機制有關,以下代碼:

// this.status 爲 pending 狀態
new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 0)
}).then(value => {
  console.log(value)
})

// this.status 爲 resolved 狀態
new MyPromise((resolve, reject) => {
  resolve(1)
}).then(value => {
  console.log(value)
})
  • 什麼是 透傳

以下面代碼,當 then 中沒有傳任何參數的時候,Promise 會使用內部默認的定義的方法,將結果傳遞給下一個 then

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolved('成功了');
  }, 1000);
})

p1.then().then((res) => {
  console.log(res);
})

由於咱們如今還沒支持鏈式調用,這段代碼運行會出問題。

支持鏈式調用

支持鏈式調用,其實很簡單,咱們只須要給 then 函數最後返回 this 就行,這樣就支持了鏈式調用:

class MyPromise {
  // ...
  private then(onFulfilled, onRejected) {
    // ...
    return this;
  }
}

每次調用 then 以後,咱們都返回當前的這個 Promise 對象,由於 Promise 對象上是存在 then 方法的,這個時候咱們就簡單的實現了 Promise 的簡單調用。

這個時候運行上面 透傳 的測試代碼了。

可是上面的代碼仍是存在相應的問題的,看下面代碼:

const p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');  
});

p1.then((res) => {
  console.log(res);
  return 'then1';
})
.then((res) => {
  console.log(res);
  return 'then2';
})
.then((res) => {
  console.log(res);
  return 'then3';
})

// 預測輸出:resolved -> then1 -> then2
// 實際輸出:resolved -> resolved -> resolved

輸出與咱們的預期有誤差,由於咱們 then 中返回的 this 表明了 p1,在 new MyPromise 以後,其實狀態已經從 pending 態變爲了 resolved 態,以後不會再變了,因此在 MyPromise 中的 this.value 值就一直是 resolved

這個時候咱們就得看看關於 then 返回值的相關知識點了。

then 返回值

實際上 then 都會返回了一個新的 Promise 對象。

先看下面這段代碼:

// 新建立一個 promise
const aPromise = new Promise(function (resolve) {
  resolve(100);
});

// then 返回的 promise
var thenPromise = aPromise.then(function (value) {
  console.log(value);
});

console.log(aPromise !== thenPromise); // => true

從上面的代碼中咱們能夠得出 then 方法返回的 Promise 已經再也不是最初的 Promise 了,以下圖(引自 Promise 迷你書):

引自 Promise 迷你書

promise 的鏈式調用跟 jQuery 的鏈式調用是有區別的, jQuery 鏈式調用返回的對象仍是最初那個 jQuery 對象; Promise 更相似於數組中一些方法,如 slice,每次進行操做以後,都會返回一個新的值。

改造代碼

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}

    // then 方法返回一個新的 promise
    const promise2 = new MyPromise((resolve, reject) => {
      // 成功狀態,直接 resolve
      if (this.status === MyPromise.RESOLVED) {
        // 將 onFulfilled 函數的返回值,resolve 出去
        let x = onFulfilled(this.value);
        resolve(x);
      }

      // 失敗狀態,直接 reject
      if (this.status === MyPromise.REJECTED) {
        // 將 onRejected 函數的返回值,reject 出去
        let x = onRejected(this.reason)
        reject && reject(x);
      }

      // 等待狀態,將 onFulfilled,onRejected 塞入數組中,等待回調執行
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push((value) => {
          let x = onFulfilled(value);
          resolve(x);
        })
        this.rejectedQueues.push((reason) => {
          let x = onRejected(reason);
          reject && reject(x);
        })
      }
    });
    return promise2;
  }
}

// 輸出結果 resolved -> then1 -> then2

存在問題

到這裏咱們就完成了簡單的鏈式調用,可是隻能支持同步的鏈式調用,若是咱們須要在 then 方法中再去進行其餘異步操做的話,上面的代碼就 GG 了。

以下代碼:

const p1 = new MyPromise((resolved, rejected) => {
  resolved('我 resolved 了');  
});

p1.then((res) => {
  console.log(res);
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then1');
    }, 1000)
  });
})
.then((res) => {
  console.log(res);
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then2');
    }, 1000)
  });
})
.then((res) => {
  console.log(res);
  return 'then3';
})

上面的代碼會直接將 Promise 對象直接看成參數傳給下一個 then 函數,而咱們實際上是想要將這個 Promise 的處理結果傳遞下去。

 

第三版(異步鏈式調用)

這一版咱們來實現 promise 的異步鏈式調用。

思路

先看一下 thenonFulfilledonRejected 返回的值:

// 成功的函數返回
let x = onFulfilled(this.value);

// 失敗的函數返回
let x = onRejected(this.reason);

從上面的的問題中能夠看出,x 能夠是一個 普通值,也能夠是一個 Promise 對象,普通值的傳遞咱們在 第二版 已經解決了,如今須要解決的是當 x 返回一個 Promise 對象的時候該怎麼處理。

其實也很簡單,當 x 是一個 Promise 對象的時候,咱們須要進行等待,直到返回的 Promise 狀態變化的時候,再去執行以後的 then 函數,代碼以下:

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason}

    // then 方法返回一個新的 promise
    const promise2 = new MyPromise((resolve, reject) => {
      // 成功狀態,直接 resolve
      if (this.status === MyPromise.RESOLVED) {
        // 將 onFulfilled 函數的返回值,resolve 出去
        let x = onFulfilled(this.value);
        resolvePromise(promise2, x, resolve, reject);
      }

      // 失敗狀態,直接 reject
      if (this.status === MyPromise.REJECTED) {
        // 將 onRejected 函數的返回值,reject 出去
        let x = onRejected(this.reason)
        resolvePromise(promise2, x, resolve, reject);
      }

      // 等待狀態,將 onFulfilled,onRejected 塞入數組中,等待回調執行
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push(() => {
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.rejectedQueues.push(() => {
          let x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        })
      }
    });
    return promise2;
  }
}

咱們新寫一個函數 resolvePromise,這個函數是用來處理異步鏈式調用的核心方法,他會去判斷 x 返回值是否是 Promise 對象,若是是的話,就直到 Promise 返回成功以後在再改變狀態,若是是普通值的話,就直接將這個值 resovle 出去:

const resolvePromise = (promise2, x, resolve, reject) => {
  if (x instanceof MyPromise) {
    const then = x.then;
    if (x.status == MyPromise.PENDING) {
      then.call(x, y => {
        resolvePromise(promise2, y, resolve, reject);
      }, err => {
        reject(err);
      })
    } else {
      x.then(resolve, reject);
    }
  } else {
    resolve(x);
  }
}

代碼說明

resolvePromise

resolvePromise 接受四個參數:

  • promise2then 中返回的 promise
  • xthen 的兩個參數 onFulfilled 或者 onRejected 的返回值,類型不肯定,有多是普通值,有多是 thenable 對象;
  • resolverejectpromise2 的。

then 返回值類型

xPromise 的時,而且他的狀態是 Pending 狀態,若是 x 執行成功,那麼就去遞歸調用 resolvePromise 這個函數,將 x 執行結果做爲 resolvePromise 第二個參數傳入;

若是執行失敗,則直接調用 promise2reject 方法。

 

到這裏咱們基本上一個完整的 promise,接下來咱們須要根據 Promises/A+ 來規範一下咱們的 Promise

 

規範 Promise

前幾版的代碼筆者基本上是按照規範來的,這裏主要講幾個沒有符合規範的點。

規範 then(規範 2.2)

thenonFulfilledonRejected 須要異步執行,即放到異步任務中去執行(規範 2.2.4)

實現

咱們須要將 then 中的函數經過 setTimeout 包裹起來,放到一個宏任務中去,這裏涉及了 jsEventLoop,你們能夠去看看相應的文章,以下:

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    // ...
    // then 方法返回一個新的 promise
    const promise2 = new MyPromise((resolve, reject) => {
      // 成功狀態,直接 resolve
      if (this.status === MyPromise.RESOLVED) {
        // 將 onFulfilled 函數的返回值,resolve 出去
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        })
      }

      // 失敗狀態,直接 reject
      if (this.status === MyPromise.REJECTED) {
        // 將 onRejected 函數的返回值,reject 出去
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        })
      }

      // 等待狀態,將 onFulfilled,onRejected 塞入數組中,等待回調執行
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(err) {
              reject(err);
            }
          })
        })
        this.rejectedQueues.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject);
            } catch(err) {
              reject(err);
            }
          })
        })
      }
    });
    return promise2;
  }
}

使用微任務包裹

但這樣仍是有一個問題,咱們知道其實 Promise.then 是屬於微任務的,如今當使用 setTimeout 包裹以後,就至關於會變成一個宏任務,能夠看下面這一個例子:

var p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');
})

setTimeout(() => {
  console.log('---setTimeout---');
}, 0);

p1.then(res => {
  console.log('---then---');
})

// 正常 Promise:then -> setTimeout
// 咱們的 Promise:setTimeout -> then

輸出順序不同,緣由是由於如今的 Promise 是經過 setTimeout 宏任務包裹的。

咱們能夠改進一下,使用微任務來包裹 onFulfilledonRejected,經常使用的微任務有 process.nextTickMutationObserverpostMessage 等,咱們這個使用 postMessage 改寫一下:

// ...
if (this.status === MyPromise.RESOLVED) {
  // 將 onFulfilled 函數的返回值,resolve 出去
  // 註冊一個 message 事件
  window.addEventListener('message', event => {
    const { type, data } =  event.data;

    if (type === '__promise') {
      try {
        let x = onFulfilled(that.value);
        resolvePromise(promise2, x, resolve, reject);
      } catch(err) {
        reject(err);
      }
    }
  });
  // 立馬執行
  window.postMessage({
    type: '__promise',
  }, "http://localhost:3001");
}

// ...

實現方法很簡單,咱們監聽windowmessage 事件,並在以後立馬觸發一個 postMessage 事件,這個時候其實 then 中的回調函數已經在微任務隊列中了,咱們從新運行一下例子,能夠看到輸出的順序變爲了 then -> setTimeout

固然 Promise 內部實現確定沒有這麼簡單,筆者在這裏只是提供一種思路,你們有興趣能夠去研究一波。

規範 resolvePromise 函數(規範 2.3)

重複引用

重複引用,當 xpromise2 是同樣的,那就須要報一個錯誤,重複應用。(規範 2.3.1)



由於本身等待本身完成是永遠都不會有結果的。
const p1 = new MyPromise((resolved, rejected) => {
  resolved('我 resolved 了');  
});

const p2 = p1.then((res) => {
  return p2;
});

x 的類型

大體分爲一下這麼幾條:

  • 2.3.2:當 x 是一個 Promise,那麼就等待 x 改變狀態以後,纔算完成或者失敗(這個也屬於 2.3.3,由於 Promise 其實也是一個 thenable 對象)
  • 2.3.3:當 x 是一個對象 或者 函數的時候,即 thenable 對象,那就那 x.then 做爲 then
  • 2.3.4:當 x 不是一個對象,或者函數的時候,直接將 x 做爲參數 resolve 返回。

咱們主要看一下 2.3.3 就行,由於 Prmise 也屬於 thenable 對象,那什麼是 thenable 對象呢?

簡單來講就是具備 then方法的對象/函數,全部的 Promise 對象都是 thenable 對象,但並不是全部的 thenable 對象並不是是 Promise 對象。以下:

let thenable = {
 then: function(resolve, reject) {
   resolve(100);
 }
}

根據 x 的類型進行處理:

  • 若是 x 不是 thenable 對象,直接調用 Promise2resolve,將 x 做爲成功的結果;
  • xthenable 對象,會調用 xthen 方法,成功後再去調用 resolvePromise 函數,並將執行結果 y 做爲新的 x 傳入 resolvePromise,直到這個 x 值再也不是一個 thenable 對象爲止;若是失敗則直接調用 promise2reject
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
  if (typeof then === 'function') {
    then.call(x, (y) => {
      resolvePromise(promise2, y, resolve, reject);
    }, (err) => {
      reject(err);
    })
  }
} else {
  resolve(x);
}

只調用一次

規範( Promise/A+ 2.3.3.3.3)規定若是同時調用 resolvePromiserejectPromise,或者對同一參數進行了屢次調用,則第一個調用優先,而全部其餘調用均被忽略,確保只執行一次改變狀態。

咱們在外面定義了一個 called 佔位符,爲了得到 then 函數有沒有執行過相應的改變狀態的函數,執行過了以後,就再也不去執行了,主要就是爲了知足規範。

x 爲 Promise 對象

若是 xPromise 對象的話,其實當執行了resolve 函數 以後,就不會再執行 reject 函數了,是直接在當前這個 Promise 對象就結束掉了。

x 爲 thenable 對象

x 是普通的 thenable 函數的時候,他就有可能同時執行 resolvereject 函數,便可以同時執行 promise2resolve 函數 和 reject 函數,可是其實 promise2 在狀態改變了以後,也不會再改變相應的值了。其實也沒有什麼問題,以下代碼:

// thenable 對像
{
 then: function(resolve, reject) {
   setTimeout(() => {
     resolve('我是thenable對像的 resolve');
     reject('我是thenable對像的 reject')
    })
 }
}

完整的 resolvePromise

完整的 resolvePromise 函數以下:

const resolvePromise = (promise2, x, resolve, reject) => {
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') {
        then.call(x, y => {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if(called)return;
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if(called)return;
      called = true;
      reject(e); 
    }
  } else {
    resolve(x);
  }
}

到這裏就大功告成了,開不開心,興不興奮!

最後咱們能夠經過測試腳本跑一下咱們的 MyPromise 是否符合規範。

測試

有專門的測試腳本(promises-aplus-tests)能夠幫助咱們測試所編寫的代碼是否符合 Promise/A+ 的規範。

可是貌似只能測試 js 文件,因此筆者就將 ts 文件轉化爲了 js 文件,進行測試

在代碼裏面加上:

// 執行測試用例須要用到的代碼
MyPromise.deferred = function() {
  let defer = {};
  defer.promise = new MyPromise((resolve, reject) => {
      defer.resolve = resolve;
      defer.reject = reject;
  });
  return defer;
}

須要提早安裝一下測試插件:

# 安裝測試腳本
npm i -g promises-aplus-tests

# 開始測試
promises-aplus-tests MyPromise.js

結果以下:

完美經過,接下去咱們就能夠看看 Promise 更多方法的實現了。

 

更多方法

實現上面的 Promise 以後,其實編寫其實例和靜態方法,相對來講就簡單了不少。

實例方法

Promise.prototype.catch

實現

其實這個方法就是 then 方法的語法糖,只須要給 then 傳遞 onRejected 參數就 ok 了。

private catch(onRejected) {
  return this.then(null, onRejected);
}
例子:
const p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');
})

p1.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      rejected('錯誤了');
    }, 1000)
  });
})
.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then2');
    }, 1000)
  });
})
.then((res) => {
  return 'then3';
}).catch(error => {
  console.log('----error', error);
})

// 1s 以後輸出:----error 錯誤了

Promise.prototype.finally

實現

finally() 方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。

private finally (fn) {
  return this.then(fn, fn);
}
例子
const p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');
})

p1.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      rejected('錯誤了');
    }, 1000)
  });
})
.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then2');
    }, 1000)
  });
})
.then((res) => {
  return 'then3';
}).catch(error => {
  console.log('---error', error);
  return `catch-${error}`
}).finally(res => {
  console.log('---finally---', res);
})

// 輸出結果:---error 錯誤了" -> ""---finally--- catch-錯誤了

 

靜態方法

Promise.resolve

實現

有時須要將現有對象轉爲 Promise 對象,Promise.resolve()方法就起到這個做用。

static resolve = (val) => {
  return new MyPromise((resolve,reject) => {
    resolve(val);
  });
}
例子
MyPromise.resolve({name: 'darrell', sex: 'boy' }).then((res) => {
  console.log(res);
}).catch((error) => {
  console.log(error);
});

// 輸出結果:{name: "darrell", sex: "boy"}

Promise.reject

實現

Promise.reject(reason) 方法也會返回一個新的 Promise 實例,該實例的狀態爲 rejected

static reject = (val) => {
  return new MyPromise((resolve,reject) => {
    reject(val)
  });
}
例子
MyPromise.reject("出錯了").then((res) => {
  console.log(res);
}).catch((error) => {
  console.log(error);
});

// 輸出結果:出錯了

Promise.all

Promise.all() 方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例,

const p = Promise.all([p1, p2, p3]);
  • 只有 p1p2p3 的狀態都變成 fulfilledp 的狀態纔會變成 fulfilled
  • 只要 p1p2p3 之中有一個被 rejectedp 的狀態就變成 rejected,此時第一個被 reject 的實例的返回值,會傳遞給p的回調函數。
實現
static all = (promises: MyPromise[]) => {
  return new MyPromise((resolve, reject) => {
    let result: MyPromise[] = [];
    let count = 0;

    for (let i = 0; i < promises.length; i++) {
      promises[i].then(data => {
        result[i] = data;
        if (++count == promises.length) {
          resolve(result);
        }
      }, error => {
        reject(error);
      });
    }
  });
}
例子
let Promise1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('Promise1');
  }, 2000);
});

let Promise2 = new MyPromise((resolve, reject) => {
  resolve('Promise2');
});

let Promise3 = new MyPromise((resolve, reject) => {
  resolve('Promise3');
})

let Promise4 = new MyPromise((resolve, reject) => {
  reject('Promise4');
})

let p = MyPromise.all([Promise1, Promise2, Promise3, Promise4]);

p.then((res) => {
  // 三個都成功則成功  
  console.log('---成功了', res);
}).catch((error) => {
  // 只要有失敗,則失敗 
  console.log('---失敗了', err);
});

// 直接輸出:---失敗了 Promise4

Promise.race

Promise.race()方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。

const p = Promise.race([p1, p2, p3]);

只要 p1p2p3 之中有一個實例率先改變狀態,p 的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給 p 的回調函數。

實現
static race = (promises) => {
  return new Promise((resolve,reject)=>{
    for(let i = 0; i < promises.length; i++){
      promises[i].then(resolve,reject)
    };
  })
}
例子

例子和 all 同樣,調用以下:

// ...

let p = MyPromise.race([Promise1, Promise2, Promise3, Promise4])

p.then((res) => { 
  console.log('---成功了', res);
}).catch((error) => {
  console.log('---失敗了', err);
});

// 直接輸出:---成功了 Promise2

Promise.allSettled

此方法接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。

const p = Promise.race([p1, p2, p3]);

只有等到全部這些參數實例都返回結果,不論是 fulfilled 仍是 rejected,並且該方法的狀態只可能變成 fulfilled

此方法與 Promise.all 的區別是 all 沒法肯定全部請求都結束,由於在 all 中,若是有一個被 Promiserejectedp 的狀態就立馬變成 rejected,有可能有些異步請求還沒走完。
實現
static allSettled = (promises: MyPromise[]) => {
  return new MyPromise((resolve) => {
    let result: MyPromise[] = [];
    let count = 0;
    for (let i = 0; i < promises.length; i++) {
      promises[i].finally(res => {
        result[i] = res;
        if (++count == promises.length) {
          resolve(result);
        }
      })
    }
  });
}
例子

例子和 all 同樣,調用以下:

let p = MyPromise.allSettled([Promise1, Promise2, Promise3, Promise4])

p.then((res) => {
  // 三個都成功則成功  
  console.log('---成功了', res);
}, err => {
  // 只要有失敗,則失敗 
  console.log('---失敗了', err);
})

// 2s 後輸出:---成功了 (4) ["Promise1", "Promise2", "Promise3", "Promise4"]

 

總結

這篇文章筆者帶你們一步一步的實現了符合 Promise/A+ 規範的的 Promise,看完以後相信你們基本上也可以本身獨立寫出一個 Promise 來了。

最後經過幾個問題,你們能夠看看本身掌握的如何:

  • Promise 中是如何實現回調函數返回值穿透的?
  • Promise 出錯後,是怎麼經過 冒泡 傳遞給最後那個捕獲異常的函數?
  • Promise 如何支持鏈式調用?
  • 怎麼將 Promise.then 包裝成一個微任務?

實不相瞞,想要個贊!

 

參考文檔

 

示例代碼

示例代碼能夠看這裏:

相關文章
相關標籤/搜索