模擬實現 Promise(小白版)

来源:https://www.cnblogs.com/dasusu/archive/2019/12/16/12047873.html
-Advertisement-
Play Games

模擬實現 Promise(小白版) 本篇來講講如何模擬實現一個 Promise 的基本功能,網上這類文章已經很多,本篇筆墨會比較多,因為想用自己的理解,用白話文來講講 Promise 的基本規範,參考了這篇: "【翻譯】Promises/A+規範" 但說實話,太多的專業術語,以及基本按照標準規範格式 ...


模擬實現 Promise(小白版)

本篇來講講如何模擬實現一個 Promise 的基本功能,網上這類文章已經很多,本篇筆墨會比較多,因為想用自己的理解,用白話文來講講

Promise 的基本規範,參考了這篇:【翻譯】Promises/A+規範

但說實話,太多的專業術語,以及基本按照標準規範格式翻譯而來,有些內容,如果不是對規範的閱讀方式比較熟悉的話,那是很難理解這句話的內容的

我就是屬於沒直接閱讀過官方規範的,所以即使在看中文譯版時,有些表達仍舊需要花費很多時間去理解,基於此,才想要寫這篇

Promise 基本介紹

Promise 是一種非同步編程方案,通過 then 方法來註冊回調函數,通過構造函數參數來控制非同步狀態

Promise 的狀態變化有兩種,成功或失敗,狀態一旦變更結束,就不會再改變,後續所有註冊的回調都能接收此狀態,同時非同步執行結果會通過參數傳遞給回調函數

使用示例

var p = new Promise((resolve, reject) => {
    // do something async job
    // resolve(data); // 任務結束,觸髮狀態變化,通知成功回調的處理,並傳遞結果數據
    // reject(err);   // 任務異常,觸髮狀態變化,通知失敗回調的處理,並傳遞失敗原因
}).then(value => console.log(value))
.catch(err => console.error(err));

p.then(v => console.log(v), err => console.error(err));

上述例子是基本用法,then 方法返回一個新的 Promise,所以支持鏈式調用,可用於一個任務依賴於上一個任務的執行結果這種場景

對於同一個 Promise 也可以調用多次 then 來註冊多個回調處理

通過使用來理解它的功能,清楚它都支持哪些功能後,我們在模擬實現時,才能知道到底需要寫些什麼代碼

所以,這裡來比較細節的羅列下 Promise 的基本功能:

  • Promise 有三種狀態:Pending(執行中)、Resolved(成功)、Rejected(失敗),狀態一旦變更結束就不再改變
  • Promise 構造函數接收一個函數參數,可以把它叫做 task 處理函數
  • task 處理函數用來處理非同步工作,這個函數有兩個參數,也都是函數類型,當非同步工作結束,就是通過調用這兩個函數參數來通知 Promise 狀態變更、回調觸發、結果傳遞
  • Promise 有一個 then 方法用於註冊回調處理,當狀態變化結束,註冊的回調一定會被處理,即使是在狀態變化結束後才通過 then 註冊
  • then 方法支持調用多次來註冊多個回調處理
  • then 方法接收兩個可選參數,這兩個參數類型都是函數,也就是需要註冊的回調處理函數,分別是成功時的回調函數,失敗時的回調函數
  • 這些回調函數有一個參數,類型任意,值就是任務結束需要通知給回調的結果,通過調用 task 處理函數的參數(類型是函數)傳遞過來
  • then 方法返回一個新的 Promise,以便支持鏈式調用,新 Promise 狀態的變化依賴於回調函數的返回值,不同類型處理方式不同
  • then 方法的鏈式調用中,如果中間某個 then 傳入的回調處理不能友好的處理回調工作(比如傳遞給 then 非函數類型參數),那麼這個工作會繼續往下傳遞給下個 then 註冊的回調函數
  • Promise 有一個 catch 方法,用於註冊失敗的回調處理,其實是 then(null, onRejected) 的語法糖
  • task 處理函數或者回調函數執行過程發生代碼異常時,Promise 內部自動捕獲,狀態直接當做失敗來處理
  • new Promise(task) 時,傳入的 task 函數就會馬上被執行了,但傳給 then 的回調函數,會作為微任務放入隊列中等待執行(通俗理解,就是降低優先順序,延遲執行,不知道怎麼模擬微任務的話,可以使用 setTimeout 生成的巨集任務來模擬)

這些基本功能就足夠 Promise 的日常使用了,所以我們的模擬實現版的目標就是實現這些功能

模擬實現思路

第一步:骨架

Promise 的基本功能清楚了,那我們代碼該怎麼寫,寫什麼?

從代碼角度來看的話,無非也就是一些變數、函數,所以,我們就可以來針對各個功能點,思考下,都需要哪些代碼:

  1. 變數上至少需要:三種狀態、當前狀態(_status)、傳遞給回調函數的結果值(_value)
  2. 構造函數 constructor
  3. task 處理函數
  4. task 處理函數的兩個用於通知狀態變更的函數(handleResolve, handleReject)
  5. then 方法
  6. then 方法註冊的兩個回調函數
  7. 回調函數隊列
  8. catch 方法

task 處理函數和註冊的回調處理函數都是使用者在使用 Promise 時,自行根據業務需要編寫的代碼

那麼,剩下的也就是我們在實現 Promise 時需要編寫的代碼了,這樣一來,Promise 的骨架其實也就可以出來了:

export type statusChangeFn = (value?: any) => void;
/* 回調函數類型 */
export type callbackFn = (value?: any) => any;

export class Promise {
    /* 三種狀態 */
    private readonly PENDING: string = 'pending';
    private readonly RESOLVED: string = 'resolved';
    private readonly REJECTED: string = 'rejected';

    /* promise當前狀態 */
    private _status: string;
    /* promise執行結果 */
    private _value: string;
    /* 成功的回調 */
    private _resolvedCallback: Function[] = [];
    /* 失敗的回調 */
    private _rejectedCallback: Function[] = [];

    /**
     * 處理 resolve 的狀態變更相關工作,參數接收外部傳入的執行結果
     */
    private _handleResolve(value?: any) {}

    /**
     * 處理 reject 的狀態變更相關工作,參數接收外部傳入的失敗原因
     */ 
    private _handleReject(value?: any) {}

    /**
     * 構造函數,接收一個 task 處理函數,task 有兩個可選參數,類型也是函數,其實也就是上面的兩個處理狀態變更工作的函數(_handleResolve,_handleReject),用來給使用者來觸髮狀態變更使用
     */
    constructor(task: (resolve?: statusChangeFn, reject?: statusChangeFn) => void) {}

    /**
     * then 方法,接收兩個可選參數,用於註冊成功或失敗時的回調處理,所以類型也是函數,函數有一個參數,接收 Promise 執行結果或失敗原因,同時可返回任意值,作為新 Promise 的執行結果
     */
    then(onResolved?: callbackFn, onRejected?: callbackFn): Promise {
        return null;
    }
    
    catch(onRejected?: callbackFn): Promise {
        return this.then(null, onRejected);
    }
} 

註意:骨架這裡的代碼,我用了 TypeScript,這是一種強類型語言,可以標明各個變數、參數類型,便於講述和理解,看不懂沒關係,下麵有編譯成 js 版的

所以,我們要補充完成的其實就是三部分:Promise 構造函數都做了哪些事、狀態變更需要做什麼處理、then 註冊回調函數時需要做的處理

第二步:構造函數

Promise 的構造函數做的事,其實很簡單,就是馬上執行傳入的 task 處理函數,並將自己內部提供的兩個狀態變更處理的函數傳遞給 task,同時將當前 promise 狀態置為 PENDING(執行中)

constructor(task) {
    // 1. 將當前狀態置為 PENDING
    this._status = this.PENDING;
        
    // 參數類型校驗
    if (!(task instanceof Function)) {
        throw new TypeError(`${task} is not a function`);
    }
        
    try {
        // 2. 調用 task 處理函數,並將狀態變更通知的函數傳遞過去,需要註意 this 的處理
        task(this._handleResolve.bind(this), this._handleReject.bind(this));
    } catch (e) {
        // 3. 如果 task 處理函數發生異常,當做失敗來處理
        this._handleReject(e);
    }
}

第三步:狀態變更

Promise 狀態變更的相關處理是我覺得實現 Promise 最難的一部分,這裡說的難並不是說代碼有多複雜,而是說這塊需要理解透,或者看懂規範並不大容易,因為需要考慮一些處理,網上看了些 Promise 實現的文章,這部分都存在問題

狀態變更的工作,是由傳給 task 處理函數的兩個函數參數被調用時觸發進行,如:

new Promise((resolve, reject) => {
    resolve(1); 
});

resolve 或 reject 的調用,就會觸發 Promise 內部去處理狀態變更的相關工作,還記得構造函數做的事吧,這裡的 resolve 或 reject 其實就是對應著內部的 _handleResolve 和 _handleReject 這兩個處理狀態變更工作的函數

但這裡有一點需要註意,是不是 resolve 一調用,Promise 的狀態就一定發生變化了呢?

答案不是的,網上看了些這類文章,他們的處理是 resolve 調用,狀態就變化,就去處理回調隊列了

但實際上,這樣是錯的

狀態的變更,其實依賴於 resolve 調用時,傳遞過去的參數的類型,因為這裡可以傳遞任意類型的值,可以是基本類型,也可以是 Promise

當類型不一樣時,對於狀態的變更處理是不一樣的,開頭那篇規範裡面有詳細的說明,但要看懂並不大容易,我這裡就簡單用我的理解來講講:

  • resolve(x) 觸發的 pending => resolved 的處理:
    • 當 x 類型是 Promise 對象時:
      • 當 x 這個 Promise 的狀態變化結束時,再以 x 這個 Promise 內部狀態和結果(_status 和 _value)作為當前 Promise 的狀態和結果進行狀態變更處理
      • 可以簡單理解成當前的 Promise 是依賴於 x 這個 Promise 的,即 x.then(this._handleResolve, this._handleReject)
    • 當 x 類型是 thenable 對象(具有 then 方法的對象)時:
      • 把這個 then 方法作為 task 處理函數來處理,這樣就又回到第一步即等待狀態變更的觸發
      • 可以簡單理解成 x.then(this._handleResolve, this._handleReject)
      • 這裡的 x.then 並不是 Promise 的 then 處理,只是簡單的一個函數調用,只是剛好函數名叫做 then
    • 其餘類型時:
      • 內部狀態(_status)置為 RESOLVE
      • 內部結果(_value)置為 x
      • 模擬創建微任務(setTimeout)處理回調函數隊列
  • reject(x) 觸發的 pending => rejected 的處理:
    • 不區分 x 類型,直接走 rejected 的處理
      • 內部狀態(_status)置為 REJECTED
      • 內部結構(_value)置為 x
      • 模擬創建微任務(setTimeout)處理回調函數隊列

所以你可以看到,其實 resolve 即使調用了,但內部並不一定就會發生狀態變化,只有當 resolve 傳遞的參數類型既不是 Promise 對象類型,也不是具有 then 方法的 thenable 對象時,狀態才會發生變化

而當傳遞的參數是 Promise 或具有 then 方法的 thenable 對象時,差不多又是相當於遞歸回到第一步的等待 task 函數的處理了

想想為什麼需要這種處理,或者說,為什麼需要這麼設計?

這是因為,存在這樣一種場景:有多個非同步任務,這些非同步任務之間是同步關係,一個任務的執行依賴於上一個非同步任務的執行結果,當這些非同步任務通過 then 的鏈式調用組合起來時,then 方法產生的新的 Promise 的狀態變更是依賴於回調函數的返回值。所以這個狀態變更需要支持當值類型是 Promise 時的非同步等待處理,這條非同步任務鏈才能得到預期的執行效果

當你們去看規範,或看規範的中文版翻譯,其實有關於這個的更詳細處理說明,比如開頭給的鏈接的那篇文章里有專門一個模塊:Promise 的解決過程,也表示成 [[Resolve]](promise, x) 就是在講這個

但我想用自己的理解來描述,這樣比較容易理解,雖然我也只能描述個大概的工作,更細節、更全面的處理應該要跟著規範來,下麵就看看代碼:

/**
 * resolve 的狀態變更處理
 */
_handleResolve(value) {
    if (this._status === this.PENDING) {
        // 1. 如果 value 是 Promise,那麼等待 Promise 狀態結果出來後,再重新做狀態變更處理
        if (value instanceof Promise) {
            try {
                // 這裡之所以不需要用 bind 來註意 this 問題是因為使用了箭頭函數
                // 這裡也可以寫成 value.then(this._handleResole.bind(this), this._handleReject.bind(this))
                value.then(v => {
                    this._handleResolve(v);
                },
                err => {
                    this._handleReject(err);
                });
            } catch(e) {
                this._handleReject(e);
            }
        } else if (value && value.then instanceof Function) {
            // 2. 如果 value 是具有 then 方法的對象時,那麼將這個 then 方法當做 task 處理函數,把狀態變更的觸發工作交由 then 來處理,註意 this 的處理
            try {
                const then = value.then;
                then.call(value, this._handleResolve.bind(this), this._handleReject.bind(this));
            } catch(e) {
                this._handleReject(e);
            }
        } else {
            // 3. 其他類型,狀態變更、觸發成功的回調
            this._status = this.RESOLVED;
            this._value = value;
            setTimeout(() = {
                this._resolvedCallback.forEach(callback => {
                    callback();
                });
            });
        }
    }
}

/**
 * reject 的狀態變更處理
 */
_handleReject(value) {
    if (this._status === this.PENDING) {
        this._status = this.REJECTED;
        this._value = value;
        setTimeout(() => {
            this._rejectedCallback.forEach(callback => {
                callback();
            });
        });
    }
}

第四步:then

then 方法負責的職能其實也很複雜,既要返回一個新的 Promise,這個新的 Promise 的狀態和結果又要依賴於回調函數的返回值,而回調函數的執行又要看情況是緩存進回調函數隊列里,還是直接取依賴的 Promise 的狀態結果後,丟到微任務隊列里去執行

雖然職能複雜是複雜了點,但其實,實現上,都是依賴於前面已經寫好的構造函數和狀態變更函數,所以只要前面幾個步驟實現上沒問題,then 方法也就不會有太大的問題,直接看代碼:

/**
 * then 方法,接收兩個可選參數,用於註冊回調處理,所以類型也是函數,且有一個參數,接收 Promise 執行結果,同時可返回任意值,作為新 Promise 的執行結果
 */
then(onResolved, onRejected) {
    // then 方法返回一個新的 Promise,新 Promise 的狀態結果依賴於回調函數的返回值
    return new Promise((resolve, reject) => {
        // 對回調函數進行一層封裝,主要是因為回調函數的執行結果會影響到返回的新 Promise 的狀態和結果
        const _onResolved = () => {
            // 根據回調函數的返回值,決定如何處理狀態變更
            if (onResolved && onResolved instanceof Function) {
                try {
                    const result = onResolved(this._value);
                    resolve(result);
                } catch(e) {
                    reject(e);
                }
            } else {
                // 如果傳入非函數類型,則將上個Promise結果傳遞給下個處理
                resolve(this._value);
            }
        };
        const _onRejected = () => {
            if (onRejected && onRejected instanceof Function) {
                try {
                    const result = onRejected(this._value);
                    resolve(result);
                } catch(e) {
                    reject(e);
                }
            } else {
                reject(this._value);
            }
        };
        // 如果當前 Promise 狀態還沒變更,則將回調函數放入隊列里等待執行
        // 否則直接創建微任務來處理這些回調函數
        if (this._status === this.PENDING) {
            this._resolvedCallback.push(_onResolved);
            this._rejectedCallback.push(_onRejected);
        } else if (this._status === this.RESOLVED) {
            setTimeout(_onResolved);
        } else if (this._status === this.REJECTED) {
            setTimeout(_onRejected);
        }
    });
}

其他方面

因為目的在於理清 Promise 的主要功能職責,所以我的實現版並沒有按照規範一步步來,細節上,或者某些特殊場景的處理,可能欠缺考慮

比如對各個函數參數類型的校驗處理,因為 Promise 的參數基本都是函數類型,但即使傳其他類型,也仍舊不影響 Promise 的使用

比如為了避免被更改實現,一些內部變數可以改用 Symbol 實現

但大體上,考慮了上面這些步驟實現,基本功能也差不多了,重要的是狀態變更這個的處理要考慮全一點,網上一些文章的實現版,這個是漏掉考慮的

還有當面試遇到讓你手寫實現 Promise 時不要慌,可以按著這篇的思路,先把 Promise 的基本用法回顧一下,然後回想一下它支持的功能,再然後心裡有個大概的骨架,其實無非也就是幾個內部變數、構造函數、狀態變更函數、then 函數這幾塊而已,但死記硬背並不好,有個思路,一步步來,總能回想起來

源碼

源碼補上了 catch,resolve 等其他方法的實現,這些其實都是基於 Promise 基本功能上的一層封裝,方便使用

class Promise {
    /**
     * 構造函數負責接收並執行一個 task 處理函數,並將自己內部提供的兩個狀態變更處理的函數傳遞給 task,同時將當前 promise 狀態置為 PENDING(執行中)
     */
    constructor(task) {
        /* 三種狀態 */
        this.PENDING = 'pending';
        this.RESOLVED = 'resolved';
        this.REJECTED = 'rejected';
        /* 成功的回調 */
        this._resolvedCallback = [];
        /* 失敗的回調 */
        this._rejectedCallback = [];

        // 1. 將當前狀態置為 PENDING
        this._status = this.PENDING;

        // 參數類型校驗
        if (!(task instanceof Function)) {
            throw new TypeError(`${task} is not a function`);
        }
        try {
            // 2. 調用 task 處理函數,並將狀態變更通知的函數傳遞過去,需要註意 this 的處理
            task(this._handleResolve.bind(this), this._handleReject.bind(this));
        } catch (e) {
            // 3. 如果 task 處理函數發生異常,當做失敗來處理
            this._handleReject(e);
        }
    }

    /**
     * resolve 的狀態變更處理
     */
    _handleResolve(value) {
        if (this._status === this.PENDING) {
            if (value instanceof Promise) {
                // 1. 如果 value 是 Promise,那麼等待 Promise 狀態結果出來後,再重新做狀態變更處理
                try {
                    // 這裡之所以不需要用 bind 來註意 this 問題是因為使用了箭頭函數
                    // 這裡也可以寫成 value.then(this._handleResole.bind(this), this._handleReject.bind(this))
                    value.then(v => {
                            this._handleResolve(v);
                        },
                        err => {
                            this._handleReject(err);
                        });
                } catch(e) {
                    this._handleReject(e);
                }
            } else if (value && value.then instanceof Function) {
                // 2. 如果 value 是具有 then 方法的對象時,那麼將這個 then 方法當做 task 處理函數,把狀態變更的觸發工作交由 then 來處理,註意 this 的處理
                try {
                    const then = value.then;
                    then.call(value, this._handleResolve.bind(this), this._handleReject.bind(this));
                } catch(e) {
                    this._handleReject(e);
                }
            } else {
                // 3. 其他類型,狀態變更、觸發成功的回調
                this._status = this.RESOLVED;
                this._value = value;
                setTimeout(() => {
                    this._resolvedCallback.forEach(callback => {
                    callback();
                });
            });
            }
        }
    }

    /**
     * reject 的狀態變更處理
     */
    _handleReject(value) {
        if (this._status === this.PENDING) {
            this._status = this.REJECTED;
            this._value = value;
            setTimeout(() => {
                this._rejectedCallback.forEach(callback => {
                    callback();
                });
            });
        }
    }

    /**
     * then 方法,接收兩個可選參數,用於註冊回調處理,所以類型也是函數,且有一個參數,接收 Promise 執行結果,同時可返回任意值,作為新 Promise 的執行結果
     */
    then(onResolved, onRejected) {
        // then 方法返回一個新的 Promise,新 Promise 的狀態結果依賴於回調函數的返回值
        return new Promise((resolve, reject) => {
            // 對回調函數進行一層封裝,主要是因為回調函數的執行結果會影響到返回的新 Promise 的狀態和結果
            const _onResolved = () => {
                // 根據回調函數的返回值,決定如何處理狀態變更
                if (onResolved && onResolved instanceof Function) {
                    try {
                        const result = onResolved(this._value);
                        resolve(result);
                    } catch(e) {
                        reject(e);
                    }
                } else {
                    // 如果傳入非函數類型,則將上個Promise結果傳遞給下個處理
                    resolve(this._value);
                }
            };
            const _onRejected = () => {
                if (onRejected && onRejected instanceof Function) {
                    try {
                        const result = onRejected(this._value);
                        resolve(result);
                    } catch(e) {
                        reject(e);
                    }
                } else {
                    reject(this._value);
                }
            };
            // 如果當前 Promise 狀態還沒變更,則將回調函數放入隊列里等待執行
            // 否則直接創建微任務來處理這些回調函數
            if (this._status === this.PENDING) {
                this._resolvedCallback.push(_onResolved);
                this._rejectedCallback.push(_onRejected);
            } else if (this._status === this.RESOLVED) {
                setTimeout(_onResolved);
            } else if (this._status === this.REJECTED) {
                setTimeout(_onRejected);
            }
        });
    }

    catch(onRejected) {
        return this.then(null, onRejected);
    }

    static resolve(value) {
        if (value instanceof Promise) {
            return value;
        }
        return new Promise((reso) => {
            reso(value);
        });
    }
    
    static reject(value) {
        if (value instanceof Promise) {
            return value;
        }
        return new Promise((reso, reje) => {
            reje(value);
        });
    }
}

測試

網上有一些專門測試 Promise 的庫,可以直接藉助這些,比如:promises-tests

我這裡就舉一些基本功能的測試用例:

  • 測試鏈式調用
// 測試鏈式調用
new Promise(r => {
    console.log('0.--同步-----');
    r();
}).then(v => console.log('1.-----------------'))
.then(v => console.log('2.-----------------'))
.then(v => console.log('3.-----------------'))
.then(v => console.log('4.-----------------'))
.then(v => console.log('5.-----------------'))
.then(v => console.log('6.-----------------'))
.then(v => console.log('7.-----------------'))
輸出
0.--同步-----
1.-----------------
2.-----------------
3.-----------------
4.-----------------
5.-----------------
6.-----------------
7.-----------------
  • 測試多次調用 then 註冊多個回調處理
// 測試多次調用 then 註冊多個回調處理
var p = new Promise(r => r(1));
p.then(v => console.log('1-----', v), err => console.error('error', err));
p.then(v => console.log('2-----', v), err => console.error('error', err));
p.then(v => console.log('3-----', v), err => console.error('error', err));
p.then(v => console.log('4-----', v), err => console.error('error', err));
輸出
1----- 1
2----- 1
3----- 1
4----- 1
  • 測試非同步場景
// 測試非同步場景
new Promise(r => {
    r(new Promise(a => setTimeout(a, 5000)).then(v => 1));
})
.then(v => {
    console.log(v);
    return new Promise(a => setTimeout(a, 1000)).then(v => 2);
})
.then(v => console.log('success', v), err => console.error('error', err));
輸出
1  // 5s 後才輸出
success 2  // 再2s後才輸出

這個測試,可以檢測出 resolve 的狀態變更到底有沒有根據規範,區分不同場景進行不同處理,你可以網上隨便找一篇 Promise 的實現,把它的代碼貼到瀏覽器的 console 里,然後測試一下看看,就知道有沒有問題了

  • 測試執行結果類型為 Promise 對象場景
// 測試執行結果類型為 Promise 對象場景(Promise 狀態 5s 後變化)
new Promise(r => {
   r(new Promise(a => setTimeout(a, 5000)));
}).then(v => console.log('success', v), err => console.error('error', err));
輸出
success undefined  // 5s 後才輸出
// 測試執行結果類型為 Promise 對象場景(Promise 狀態不會發生變化)
new Promise(r => {
   r(new Promise(a => 1));
}).then(v => console.log('success', v), err => console.error('error', err));
輸出
// 永遠都不輸出
  • 測試執行結果類型為具有 then 方法的 thenable 對象場景
// 測試執行結果類型為具有 then 方法的 thenable 對象場景(then 方法內部會調用傳遞的函數參數)
new Promise(r => {
    r({
        then: (a, b) => {
            return a(1);
        }
    });
}).then(v => console.log('success', v), err => console.error('error', err));
輸出
success 1
// // 測試執行結果類型為具有 then 方法的 thenable 對象場景(then 方法內部不會調用傳遞的函數參數)
new Promise(r => {
    r({
        then: (a, b) => {
            return 1;
        }
    });
}).then(v => console.log('success', v), err => console.error('error', err));
輸出
// 永遠都不輸出
// 測試執行結果類型為具有 then 的屬性,但屬性值類型非函數
new Promise(r => {
    r({
        then: 111
    });
}).then(v => console.log('success', v), err => console.error('error', err));
輸出
success {then: 111}
  • 測試執行結果的傳遞
// 測試當 Promise rejectd 時,reject 的狀態結果會一直傳遞到可以處理這個失敗結果的那個 then 的回調中
new Promise((r, j) => {
    j(1);
}).then(v => console.log('success', v))
  .then(v => console.log('success', v), err => console.error('error', err))
  .catch(err => console.log('catch', err));
輸出
error 1
// 測試傳給 then 的參數是非函數類型時,執行結果和狀態會一直傳遞
new Promise(r => {
    r(1);
}).then(1)
.then(null, err => console.error('error', err))
.then(v => console.log('success', v), err => console.error('error', err));
輸出
success 1
// 測試 rejectd 失敗被處理後,就不會繼續傳遞 rejectd
new Promise((r,j) => {
    j(1);
}).then(2)
.then(v => console.log('success', v), err => console.error('error', err))
.then(v => console.log('success', v), err => console.error('error', err));
輸出
error 1
success undefined

最後,當你自己寫完個模擬實現 Promise 時,你可以將代碼貼到瀏覽器上,然後自己測試下這些用例,跟官方的 Promise 執行結果比對下,你就可以知道,你實現的 Promise 基本功能上有沒有問題了

當然,需要更全面的測試的話,還是得藉助一些測試庫

不過,自己實現一個 Promise 的目的其實也就在於理清 Promise 基本功能、行為、原理,所以這些用例能測通過的話,那麼基本上也就掌握這些知識點了


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本小節主要講解 HTML 的基本信息,其中包含 HTML 概念、HTML 發展歷程和 HTML 標準版本的發展歷程。通過本小節的學習可以對 HTML 的含義有個初步的理解,為後續進一步學習 HTML 相關概念打下基礎。 ...
  • Step1. 文檔介紹 1 // vue-cli css預處理文檔: https://cli.vuejs.org/zh/guide/css.html#自動化導入 2 // less文檔: https://www.w3cschool.cn/less/less_variables_overview.ht ...
  • 一、CSS樣式 解決文字過長顯示省略號問題 1、一般樣式 一般 css 樣式,當寬度不夠時,可能會出現換行的效果。這樣的效果在某些時候肯定是不行的,可以修改 css 樣式來解決這個問題。 <!DOCTYPE html> <html> <head> <meta http-equiv="Content- ...
  • 對應生成的dist文件目錄及多頁面配置時的文件目錄如下: ...
  • 引子 textarea 中的換行格式,在其它地方顯示時,需要保持其原有的換行格式。 [Origin][url origin] [My GitHub][url my github] 換行 textarea 元素支持多行純文本編輯。由於歷史原因,元素的值有三種不同的形式: row value 是其原始設 ...
  • Infi-chu: http://www.cnblogs.com/Infi-chu/ 一、什麼是Vue1.Vue.js是一個構建數據驅動的Web界面的漸進式框架2.Vue.js的目標是通過儘可能簡單的API實現響應的數據綁定和組合的視圖組件3.核心是一個響應的數據綁定系統二、基本使用eg.<!DOC ...
  • JQuery輪播圖 ...
  • 1、vue初時 vue安裝三種方式: 1:CDN引入 以下推薦國外比較穩定的兩個 CDN,國內還沒發現哪一家比較好,目前還是建議下載到本地。 Staticfile CDN(國內) : https://cdn.staticfile.org/vue/2.2.2/vue.min.js unpkg:http ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...