一杯喜茶的時間手搓Promise

做者: JowayYoung
倉庫: GithubCodePen
博客: 掘金思否知乎簡書頭條CSDN
公衆號: IQ前端
聯繫我:關注公衆號後有個人 微信
特別聲明:原創不易,未經受權不得對此文章進行轉載或抄襲,不然按侵權處理,如需轉載或開通公衆號白名單可聯繫我,但願各位尊重原創的知識產權

本文由筆者師妹LazyCurry創做,收錄於筆者技術文章專欄下前端

前言

咱們都知道,JS是單線程的,只有前一個任務結束,才能執行下一個任務。顯然在瀏覽器上,這樣執行會堵塞瀏覽器對DOM的渲染。因此,JS中會有不少異步操做,那JS是如何實現異步操做呢?這就要想到Promise對象了,文本先來認識Promise,再手寫代碼實現Promise。git

認識Promise

Promise是JS解決異步編程的方法之一,其英文意思是承諾。在程序中可理解爲等一段時間就會執行,等一段時間就是JS中的異步。異步是指須要比較長的時間才能執行完成的任務,例如網絡請求,讀取文件等。Promise是一個實例對象,可從中獲取異步處理的結果。github

Promise有3種狀態,分別是pending(進行中)、fulfilled(已成功)、rejected(已失敗)。只有異步操做可改變Promise的狀態,其餘操做都沒法改變。而且狀態改變後就不會再變,只能是從pendingfulfiledpendingrejected,這也是Promise一個比較鮮明的特色。編程

使用Promise

上述已說到,Promise是一個對象,那麼它確定是由其構造函數來建立。其構造函數接受一個函數做爲參數,其函數的參數有2個,分別是resolverejectresolve將狀態從pending變爲fulfiled,成功時調用。reject將狀態從pending變爲rejected,失敗時調用。segmentfault

function RunPromise(num, time) {
    return new Promise((resolve, reject) => {
        console.log("開始執行");
        if (num % 2 === 0) {
            setTimeout(() => {
                resolve(`偶數時調用resolve,此時num爲${num}`);
            }, time);
        } else {
            setTimeout(() => {
                reject(new Error(`奇數時調用rejected,此時num爲${num}`));
            }, time);
        }
    });
}

Promise對象上有then()catch()方法。then()接收2個參數,第一個對應resolve的回調,第二個對應reject的回調。catch()then()的第二個參數同樣,用來接受reject的回調,可是還有一個做用,若是在then()中執行resolve回調時拋出異常,這個異常多是代碼定義拋出,也多是代碼錯誤,而這個異常會在catch()被捕獲到。數組

RunPromise(22, 2000)
    .then(res => {
        console.log("then的第一個參數執行");
        console.log(res);
        console.log(newres);
    }, error => {
        console.log("then的第二個參數執行");
        console.log(error);
    })
    .catch(error => {
        console.log("error");
        console.log(error);
    });

// 輸出結果以下:
// 開始執行
// then的第一個參數執行
// 偶數時調用resolve,此時num爲22
// error
// ReferenceError: newres is not defined

上面例子中,RunPromise()調用resolvethen()的第一個參數對應回調,狀態從pending改爲fulfilled,且狀態不會再改變。在then()中,newres這個變量還沒有定義,所以程序出錯,其異常在catch()被捕獲。通常來講,then()使用第一個參數便可,由於catch()then()的第二個參數同樣,還能捕獲到異常。promise

實現Promise

Promise大體已瞭解清楚,也知道如何使用。爲了瞭解Promise是如何實現的,咱們手寫實現一個簡單的Promise方法,簡單地實現then()異步處理鏈式調用。用最簡單的思考方法,函數是爲了實現什麼功能,給對應函數賦予相應的實現代碼便可。如下代碼均使用ES6進行書寫。瀏覽器

定義Promise構造函數

建立Promise對象使用new Promise((resolve, reject) => {}),可知道Promise構造函數的參數是一個函數,咱們將其定義爲implement,函數帶有2個參數:resolvereject,而這2個參數又可執行,因此也是一個函數。微信

聲明完成後,須要解決狀態。上述已說過,Promise有3種狀態,這裏再也不細說,直接上代碼。網絡

// ES6聲明構造函數
class MyPromise {
    constructor(implement) {
        this.status = "pending"; // 初始化狀態爲pending
        this.res = null; // 成功時的值
        this.error = null; // 失敗時的值
        const resolve = res => {
            // resolve的做用只是將狀態從pending轉爲fulfilled,並將成功時的值存在this.res
            if (this.status === "pending") {
                this.status = "fulfilled";
                this.res = res;
            }
        };
        const reject = error => {
            // reject的做用只是將狀態從pending轉爲rejected,並將失敗時的值存在this.error
            if (this.status === "pending") {
                this.status = "rejected";
                this.error = error;
            }
        };
        // 程序報錯時會執行reject,因此在這裏加上錯誤捕獲,直接執行reject
        try {
            implement(resolve, reject);
        } catch (err) {
            reject(err);
        }
    }
}
then函數

咱們在使用Promise時,都知道then()有2個參數,分別是狀態爲fulfilledrejected時的回調函數,咱們在這裏將2個函數定義爲onFulfilledonRejected

class MyPromise {
    constructor(implement) { ... }
    then(onFulfilled, onRejected) {
        // 當狀態爲fulfilled時,調用onFulfilled並傳入成功時的值
        if (this.status === "fulfilled") {
            onFulfilled(this.res);
        }
        // 當狀態爲rejected時,調用onRejected並傳入失敗時的值
        if (this.status === "rejected") {
            onRejected(this.error);
        }
    }
}
異步處理

到這裏已實現了基本的代碼,可是異步時會出現問題。例如,本文一開始舉例使用Promise時,resolvesetTimeout()中使用,這時候在then()裏,狀態仍是pending,那就沒辦法調用到onFulfilled。因此咱們先將處理函數(onFulfilledonRejected)保存起來,等到then()被調用時再使用這些處理函數。

由於Promise可定義多個then(),因此這些處理函數用數組進行存儲。實現思路:

  • then()增長狀態爲pending的判斷,在此時存儲處理函數
  • resolvereject時循環調用處理函數
class MyPromise {
    constructor(implement) {
        this.status = "pending";
        this.res = null;
        this.error = null;
        this.resolveCallbacks = []; // 成功時回調的處理函數
        this.rejectCallbacks = []; // 失敗時回調的處理函數
        const resolve = res => {
            if (this.status === "pending") {
                this.status = "fulfilled";
                this.res = res;
                this.resolveCallbacks.forEach(fn => fn()); // 循環執行成功處理函數
            }
        };
        const reject = error => {
            if (this.status === "pending") {
                this.status = "rejected";
                this.error = error;
                this.rejectCallbacks.forEach(fn => fn()); // 循環執行失敗處理函數
            }
        };
        try {
            implement(resolve, reject);
        } catch (err) {
            reject(err);
        }
    }
    then(onFulfilled, onRejected) {
        if (this.status === "fulfilled") {
            onFulfilled(this.res);
        }
        if (this.status === "rejected") {
            onRejected(this.error);
        }
        // 當狀態爲pending時,說明這時尚未調用到resolve或reject
        // 在這裏把成功函數和失敗函數存至相應的數組中,不作執行操做只作存儲操做
        if (this.status === "pending") {
            this.resolveCallbacks.push(() => onFulfilled(this.res));
            this.rejectCallbacks.push(() => onRejected(this.error));
        }
    }
}

測試一下異步功能,打印結果中,'執行resolve'是等待了2秒後打印出來的

new MyPromise((resolve, reject) => {
    console.log("開始執行");
    setTimeout(() => {
        resolve("執行resolve");
    }, 2000);
}).then(res => console.log(res));

// 輸出結果以下:
// 開始執行
// 執行resolve
鏈式調用

到這裏就已實現異步操做啦!吼吼~可是,咱們都知道,Promise能定義多個then,就例如new Promise().then().then(),這種就是鏈式調用。固然咱們也要實現這個功能。

鏈式調用是指Promise在狀態是fulfilled後,又開始執行下一個Promise。要實現這個功能,咱們只須要在then()裏返回Promise就行了,提及來好像是挺簡單的。

then()的實現思路:

  • then()中須要返回Promise對象,咱們將其命名爲nextPromise
  • 仍然須要判斷狀態,執行相應處理
  • onFulfilledonRejected是異步調用,用setTimeout(0)解決
  • 須要對onFulfilledonRejected類型作判斷,並作相應返回
class MyPromise {
    constructor(implement) { ... }
    then(onFulfilled, onRejected) {
        // 若是onRejected不是函數,就直接拋出錯誤
        onFulfilled = typeof onFulfilled === "function" ? onFulfilled : res => res;
        onRejected = typeof onRejected === "function" ? onRejected : err => { throw err; };
        const nextPromise = new MyPromise((resolve, reject) => {
            if (this.status === "fulfilled") {
                // 解決異步問題
                setTimeout(() => {
                    const x = onFulfilled(this.res);
                    RecursionPromise(nextPromise, x, resolve, reject);
                }, 0);
            }
            if (this.status === "rejected") {
                setTimeout(() => {
                    const x = onRejected(this.error);
                    RecursionPromise(nextPromise, x, resolve, reject);
                }, 0);
            }
            if (this.status === "pending") {
                this.resolveCallbacks.push(() => {
                    setTimeout(() => {
                        const x = onFulfilled(this.res);
                        RecursionPromise(nextPromise, x, resolve, reject);
                    }, 0);
                });
                this.rejectCallbacks.push(() => {
                    setTimeout(() => {
                        const x = onRejected(this.error);
                        RecursionPromise(nextPromise, x, resolve, reject);
                    }, 0);
                });
            }
        });
        return nextPromise;
    }
}

RecursionPromise()用來判斷then()的返回值,以決定then()向下傳遞的狀態走resolve仍是reject,實現思路:

  • nextPromisex不能相等,不然會一直調用本身
  • 判斷x的類型,若是不是函數或對象,直接resolve(x)
  • 判斷x是否擁有then(),而且若是then()是一個函數,那麼就可執行xthen(),而且帶有成功與失敗的回調
  • flag的做用是執行xthen()時成功與失敗只能調用一次
  • 執行xthen(),成功時繼續遞歸解析
  • 若是then()不是一個函數,直接resolve(x)
function RecursionPromise(nextPromise, x, resolve, reject) {
    if (nextPromise === x) return false;
    let flag;
    if (x !== null && (typeof x === "object" || typeof x === "function")) {
        try {
            let then = x.then;
            if (typeof then === "function") {
                then.call(x, y => {
                    if (flag) return false;
                    flag = true;
                    // 這裏說明Promise對象resolve以後的結果仍然是Promise,那麼繼續遞歸解析
                    RecursionPromise(nextPromise, y, resolve, reject);
                }, error => {
                    if (flag) return false;
                    flag = true;
                    reject(error);
                });
            } else {
                resolve(x);
            }
        } catch (e) {
            if (flag) return false;
            flag = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}

總結

具備異步處理鏈式調用的Promise已實現啦!還有一些方法在這裏就不一一實現了。畢竟實現一個完整的Promise不是一篇文章就能講完的,有興趣的同窗可自行參照Promise的功能進行解構重寫,如有寫得不正確的地方請各位大佬指出。公衆號後臺回覆promise可獲取本文的源碼,若是是轉載的文章,可關注IQ前端再回復promise便可。

寫這篇文章的目的是爲了給各位同窗提供一個函數解構的思路,學會去分析一個函數的功能,從而解構出每個步驟是如何執行和實現的,祝你們學習愉快,下次再見~

結語

❤️關注+點贊+收藏+評論+轉發❤️,原創不易,鼓勵筆者創做更好的文章

關注公衆號IQ前端,一個專一於CSS/JS開發技巧的前端公衆號,更多前端小乾貨等着你喔

  • 關注後回覆關鍵詞免費領取視頻教程
  • 關注後添加我微信拉你進技術交流羣
  • 歡迎關注IQ前端,更多CSS/JS開發技巧只在公衆號推送

相關文章
相關標籤/搜索