主頁 > 企業開發 > 模擬實作 Promise(小白版)

模擬實作 Promise(小白版)

2020-10-08 05:59:32 企業開發

模擬實作 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 =https://www.cnblogs.com/dasusu/p/> 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 = https://www.cnblogs.com/dasusu/p/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 = https://www.cnblogs.com/dasusu/p/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 基本功能、行為、原理,所以這些用例能測通過的話,那么基本上也就掌握這些知識點了

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/162502.html

標籤:JavaScript

上一篇:批量洗掉資料

下一篇:GIT常用命令

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more