Promise原理分析

Promise成爲你們解決異步回調地獄問題的重要方法,理解它的原理和底層實現有助於咱們更加合理的使用。如下遵循PromiseA+規範實現了一個簡單的Promise.jquery

實現分爲六步:數組

  • 實現同步promise
  • 實現異步promise和屢次then
  • 實現promise的鏈式調用
  • 特殊狀況的處理
  • 實現then穿透
  • 測試

實現同步的promise

  • 明確promise有三個狀態: pending fulfilled rejectedpromise

  • 在new Promise時, excutor會當即執行bash

  • 每一個promise上會有一個then方法, then方法中傳遞兩個參數onFulfilled和onRejected異步

代碼以下:測試

class Promise{
    constructor(excutor){
        this.status = 'pending'; // 默認是等待態
        this.value = undefined;
        this.reason = undefined;
        let resolve = (value) => { // 調用resolve, 變成成功態
            if (this.status === 'pending') {
                this.status= 'fulfilled';
                this.value = value;
            }  
        };
        let rejected = (reason) => { // 調用reject, 變成失敗態
            if (this.status === 'pending') {
                this.status= 'rejected';
                this.reason = reason;
            }         
        };
        excutor(resolve, rejected);
    }
    then(onFulfilled, onRejected){
        console.log(this.status, 'status');
        if (this.status === 'fulfilled') { // 變成成功態fulfilled時執行成功回調
            onFulfilled(this.value);
        }
        if (this.status === 'rejected') { //  變成失敗態rejected時執行失敗回調
            onFulfilled(this.reason);
        }
    }
}
複製代碼

實現異步promise和多個then

核心思路: 將成功和失敗的回調放在數組中, 當狀態改變時, 執行對應狀態的事件ui

class Promise{
    constructor(excutor){
        this.status = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.onFulfilledCbs = []; // 存放成功狀態的回調
        this.onRejectedCbs = []; // 存放失敗狀態的回調
        let resolve = (value) => {
            if (this.status === 'pending') {
                this.status= 'fulfilled';
                this.value = value;
                this.onFulfilledCbs.forEach(cb => cb()); // 依次執行成功回調
            }  
        };
        let rejected = (reason) => {
            if (this.status === 'pending') {
                this.status= 'rejected';
                this.reason = reason;
                this.onRejectedCbs.forEach(cb => cb());   // 依次執行失敗回調
            }  
        };
        excutor(resolve, rejected)
    }
    then(onFulfilled, onRejected){
        ...
        if (this.status === 'pending') { // 有異步邏輯時,狀態爲pending 將callback放在數組中
            this.onFulfilledCbs.push(() => {
                onFulfilled(this.value);
            });
            this.onRejectedCbs.push(() => {
                onRejected(this.reason);
            });
        }
    }
}
複製代碼

實現then的鏈式調用

思路: 返回一個新的promise實例. jquery中的方法也能鏈式調用, 是經過return this實現的.可是promise不能返回this, 由於, 一個promise的狀態若是是fulfilled,就不能變成rejected.this

then的特色:spa

  • 不管成功仍是失敗 會將成功或失敗的值傳遞給下一級
  • 若是沒有接受到返回值 會默認執行下個then的
  • 若是拋出錯誤,沒有捕獲,會報錯
  • 主動拋出異常 走onRejected 此時能夠將onFulfilled賦值爲null
  • 主動拋出異常也能夠經過catch進行捕獲 而且 then()能夠穿透
  • catch後能夠執行then 返回值會傳給下個onFulfilled
  • err捕獲的原則: 先找最近的捕獲,沒有找下一個
class Promise{
    constructor(excutor){
        ...
        try{ // 1. 在執行excutor時有可能拋出錯誤 直接捕獲錯誤
            excutor(resolve, reject);
        }catch(err) {
            reject(err);
        }
        
    }
    // resolvePromise 判斷當前promise返回結果x和promise2的關係
    resolvePromise(promise2, x, resolve, reject) {
        if((typeof x === 'object' && x !== null) || typeof x === 'function'){
            try{ // 取then方法時  有可能報錯 
                let then = x.then;
                if (typeof then === 'function') { // 2.2.2 返回一個promise 執行promise
                    then.call(x, (y) => {
                        resolve(y);
                    }, (r) => {
                        reject(r);
                    });
                } else { // 2.2.3 返回一個帶有then屬性的普通對象 {then: ...}
                    resolve(x);
                }
            }catch(e){
                reject(e);
            }
        } else {// 2.2.1 返回一個常量
            resolve(x);
        }
    }
   
    then(onFulfilled, onRejected){ 
        // 2.實現then的鏈式調用(jq)
        let promise2 = new Promise((resolve, reject) => { // 2.1 返回一個新的promise
            if (this.status === 'fulfilled') {
                try{ // 若是方法執行報錯 直接拋出錯誤
                    let x = onFulfilled(this.value); // 2.2 處理前一個promise中onFulfilled的不一樣返回結果和下一個promise的關係: 普通值/promise
                    this.resolvePromise(promise2, x, resolve, reject);
                }catch(e){
                    reject(e);
                }
            }
            if (this.status === 'rejected') {
                try{
                    let x = onRejected(this.reason);
                    this.resolvePromise(promise2, x, resolve, reject);
                }catch(e){
                    reject(e);
                }
            }
            if (this.status === 'pending') {
                this.onFulfilledCbs.push(() => {
                    try{
                        let x = onFulfilled(this.value);
                        this.resolvePromise(promise2, x, resolve, reject);
                    }catch(e){
                        reject(e);
                    }
                });
                this.onRejectedCbs.push(() => {
                    try{
                        let x = onRejected(this.reason);
                        this.resolvePromise(promise2, x, resolve, reject);
                    }catch(e){
                        reject(e);
                    }
                });
            }
        });
        return promise2;
    }
}
複製代碼

特殊狀況處理

  • 返回的promise2和當前的promise不能相同: 若是相同會致使promise2的循環調用
  • new Promise(resolve, reject)中resolve傳遞的值可能也是一個promise, 須要實現遞歸解析
  • 避免promise即調用成功又調用失敗
class Promise{
    ...
    resolvePromise(promise2, x, resolve, reject) {
        if (promise2 === x) { // 4.1 return的值不能爲promise2(本身等待本身) 不然會報循環引用錯誤
            return reject(new Error('循環引用'));
        }
        let called; // 4.4 定義called 避免成功失敗只能調用一個
        if((typeof x === 'object' && x !== null) || typeof x === 'function'){
            try{
                let then = x.then;
                if (typeof then === 'function') {
                    then.call(x, (y) => {
                        if (called) return; // 成功和失敗只會掉一個
                        called = true;
                        this.resolvePromise(promise2, y, resolve, reject); // 4.3 若是y是一個promise 進行遞歸解析
                    }, (r) => {
                        if (called) return; // 若是掉過成功
                        called = true;
                        reject(r);
                    });
                } else {
                    if (called) return;
                    called = true;
                    resolve(x);
                }
            }catch(e){
                if (called) return;
                called = true;
                reject(e);
            }
        } else {
            if (called) return;
            called = true;
            resolve(x);
        }
    }
   
    then(onFulfilled, onRejected){
        let promise2 = new Promise((resolve, reject) => {
            if (this.status === 'fulfilled') {
                setTimeout(() => { // 4.2 若是不加setTimeout會致使promise2爲undefined-->then方法都是異步的
                    try{
                        let x = onFulfilled(this.value);
                        this.resolvePromise(promise2, x, resolve, reject); // 初始化promise2未完成, 爲undefined
                    }catch(e){
                        reject(e);
                    }
                });
            }
            if (this.status === 'rejected') {
                setTimeout(() => {
                    try{
                        let x = onRejected(this.reason);
                        this.resolvePromise(promise2, x, resolve, reject);
                    }catch(e){
                        reject(e);
                    }
                });
            }
            ....
        });
        return promise2;
    }
}

module.exports = Promise;
複製代碼

then穿透

思路: 若是promise執行resolve, 就將resolve中的值經過onFULfilled傳遞; 若是promise執行reject, 就將reject中的值經過onRejected傳遞插件

then(onFulfilled, onRejected){
        // 5 穿透實現
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (data) => {
            return data;
        };
        onRejected = typeof onRejected === 'function' ? onRejected : (err) => {
            throw err;
        };
        ...
    }
複製代碼

測試

在promise A+規範中提出, 經過promises-aplus-tests插件對本身寫的promise進行檢驗, 可是這個插件使用前須要經過promise.defer將promise的屬性掛載在defer上, 代碼以下:

Promise.deferred = Promise.defer = () => {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}
複製代碼

下面就能夠經過promises-aplus-tests插件對本身寫的promise進行檢驗了:

  • yarn add promises-aplus-tests
  • npx promises-aplus-tests promise.js
相關文章
相關標籤/搜索