你也可以手寫自己的Promise(一)

getvalue發表於2019-02-25

我寫這篇文章不打算介紹Promise產生的原因以及它解決的問題,我只是想寫一篇關於實現自己Promise的文章。如果程式碼以及邏輯有什麼不對的地方,請大家指出來。就這些,開始正題。

前提:我們要知道Promise是基於Promises/A+規範的。其中好多變數和方法名都是從這裡來的。
我們先從Promise的使用開始,寫幾個測試例子。

let promise = new Promise((resolve, reject) =>{
    // console.log("1");
    // resolve("成功");
    // reject("失敗");
    // console.log("2");// 第一步


    // reject("失敗");
    // resolve("成功");// 第二步

    // setTimeout(() => {
    //     resolve("success");
    // }, 2000);

    throw new Error("手動丟擲錯誤");// 第四步
});
promise.then((value) => {
    console.log("then第一個方法:"+value);
}, (err) => {
    console.log("then第二個方法:"+err);
})
promise.then((value) => {
    console.log("then第一個方法:"+value);
}, (err) => {
    console.log("then第二個方法:"+err);
})
console.log("3");
複製程式碼

第一步輸出

  • 1
  • 2
  • 3
  • then第一個方法:成功
  • then第一個方法:成功

第二步輸出

  • 3
  • then第二個方法:失敗
  • then第二個方法:失敗

第三步輸出

  • 3
    兩秒之後
  • then第一個方法:success
  • then第一個方法:success

第四步輸出

  • 3
  • then第二個方法:Error: 手動丟擲錯誤
  • then第二個方法:Error: 手動丟擲錯誤

最後輸出“成功”說明 then是非同步執行的

根據以上幾個例子我們可以推出以下幾點內容:

  • Promise是一個建構函式(使用了 new)
  • Promise接收一個引數,並且這個引數是一個函式(為了方便描述,我們稱之為 executor)
  • executor在 new Promise時執行
  • new Promise中可以支援非同步行為(第三步)
  • executor有兩個引數(resolve,reject)
  • resolve和reject不會我們傳進去的,說明是屬於Promise內容提供的
  • resolve和reject都是函式,並且都接收一個引數。看規範:resolve接收引數稱之為value,reject接收引數稱之為reason
  • 每個Promise例項上都有then方法
  • then方法是非同步的
  • then方法中有兩個引數onFulfilled和onRejected 分別是成功的回撥(執行resolve)和失敗的回撥(執行reject)。看這裡
  • 一個Promise中resolve和reject只會執行一個,規範中有提到 Promise States,大家可以看下
  • 同一個promise的例項可以then多次,成功時回撥用所有的成功方法,失敗時回撥用所有的失敗方法
  • 如果發現錯誤就會走入失敗態

這麼一大坨東西,看著有點亂。我們就根據我們得出的結論開始寫屬於自己的Promise。寫的過程中思路慢慢就清晰了。

let myPromise = function (executor) {
    let self = this;//快取一下this

    self.status = `pending`;// 狀態管理 狀態的變化只能由pending變為resolved或者rejected。一件事情不能既成功又失敗。所以resolved和rejected不能相互轉化。
    self.value = undefined;// 成功後的值 傳給resolve
    self.reason = undefined;//失敗原因 傳給reject

    self.onResolvedCallbacks = [];// 存放then中成功的回撥
    self.onRejectedCallbacks = []; // 存放then中失敗的回撥 
    // 這裡說明一下,第三步使用定時器。執行完 new Promise 之後,會執行then方法,此時會把then中的方法快取起來,並不執行:此時狀態還是pending。等到定時器2秒之後,執行
    // resolve|reject 時,而是依次執行存放在陣列中的方法。 參考釋出訂閱模式

    function resolve(value) {
        // pending => resolved
        if (self.status === `pending`) {
            self.value = value;
            self.status = `resolved`;
            // 依次執行快取的成功的回撥
            self.onResolvedCallbacks.forEach(fn => fn(self.value));
        }
    }

    function reject(reason) {
        // pending => rejected
        if (self.status === `pending`) {
            self.value = value;
            self.status = `rejected`;
            // 依次執行快取的失敗的回撥
            self.onRejectedCallbacks.forEach(fn => fn(self.reason));
        }
    }

    try {
        //new Promise 時 executor執行
        executor(resolve, reject);
    } catch (error) {
        reject(error);// 當executor中執行有異常時,直接執行reject
    }
}

// 每個Promise例項上都有then方法
Promise.prototype.then = function (onFulfilled, onRejected) {
    let self = this;

    // 執行了 resolve
    if (self.status === `resolved`) {
        // 執行成功的回撥
        onFulfilled(self.value);
    }

    // 執行了 reject
    if (self.status === `rejected`) {
        // 執行失敗的回撥
        onRejected(self.reason);
    }

    // new Promise中可以支援非同步行為 當既不執行resolve又不執行reject時 狀態是預設的等待態pending
    if (self.status === `pending`) {
        // 快取成功的回撥
        self.onResolvedCallbacks.push(onFulfilled);
        // 快取失敗的回撥
        self.onRejectedCallbacks.push(onRejected);
    }
};
複製程式碼

說明一下:這是最簡版,因為Promise的強大之處是鏈式呼叫。我們這個只是雛形,由於時間關係。我們先到這裡。下一次我們基於這個雛形實現符合Promises/A+規範的完整版。

第一次發表文章,希望各位大蝦多多支援。

相關文章