手寫Promise看着一篇就足夠了


概要

本文主要介紹了Promise上定義的api的特性,以及手寫如何實現這些特性。目的是把學習過程中的產出以博客的方式輸出,鞏固知識,也便於之后復習

博客思路

mdn上搜索Promise,了解類和api的定義:

  • 定義了哪些屬性,分別代表什么含義
  • api需要傳什么參數,返回什么值,可能拋出什么異常
  • 看官方給出的用例,猜想內部可能的實現
  • 編寫源碼,用官方用例驗證查看返回值是否一致

API的特性與手寫源碼

構造函數

  • promise有狀態pending、rejectedresolved,所以應該有個變量來保存狀態
  • 構造函數參數excutor是個同步執行的回調函數,函數執行的參數是兩個函數resolved和rejected,所以promise內部需要定義兩個函數,並且在構造行數中執行excutor的地方傳入
  • .then中會傳入回調函數onResolved和onRejected,在resolved和rejected內會分別會觸發對應的回調函數,所以需要兩個數組保存then中傳進來的回調
  • resolved和rejected只能執行一次,執行后promise的狀態會改變,且參數會傳遞給回調函數
  • onRejected和onResolved異步執行
  • excutor執行拋異常會直接執行rejected,所以excutor的執行需要catch錯誤

const PENDING = "PENDING";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(excutor){

    // promise內部保存着狀態值
    this.status = PENDING;
    this.data = null;
    // then方法傳進來的回調函數此保存
    this.onResolvedList = [];
    this.onRejectedList = [];

    let resolved = (value) => {
        // resolved函數只能執行一次,所以先判斷狀態是不是pending
        if(this.status !== PENDING){
            return;
        }
        // 變更狀態為resolved
        this.status = RESOLVED;
        // 數據為傳進來的值
        this.data = value;

        // 判斷是否已經有onResolved回調已經穿入,有則異步執行
        if(this.onResolvedList.length > 0){
            setTimeout(() => {
                this.onResolvedList.forEach(onResolved => {
                    onResolved(value);
                });
            }, 0);
        }
    }

    let rejected = (reason) => {
        if(this.status !== PENDING){
            return
        }

        this.status = REJECTED;
        this.data = reason;

        if(this.onRejectedList.length > 0){
            setTimeout(() => {
                this.onRejectedList.forEach(onRejected => {
                    onRejected(reason);
                });
            });
        }
    }

    try{
        // 執行器函數同步執行,且參數為promise內定義的resolve和rejected
        excutor(resolved, rejected);
    }catch(error){
        // 如果執行器函數出錯直接執行rejected
        this.rejected(error);
    }
}

then

  • then會接受兩個回調函數onResolved和onRejected
  • onResolved和onRejected會異步調用
  • then會返回一個新的promise對象
  • then的參數如果沒傳,那么value和reason繼續向下傳遞
  • 如果執行then的時候,promise的狀態還是pending,那么只保存回調,並且確保回調執行后能修改新的promise的狀態
  • 如果觸發的對應的回調函數執行拋異常,那么返回的新的回調函數狀態為rejected,reason則會catch到的error
  • 如果觸發的對應回調函數執行返回值不是promise對象,那么返回新的promise狀態為resolved,value則為傳入then的回調的返回值
  • 如果觸發的對應回調返回值是promise對象,那么新的promise返回值的狀態取決於改回調返回的promise
MyPromise.prototype.then = function(onResolved, onRejected){
    
    // 如果沒有傳onResolved,則設置onResolved為返回value的函數
    onResolved = typeof onResolved === "function" ? onResolved : value => value
    // 如果沒有傳onRejected,則設置onRejected為拋處reason的函數
    onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason}

    return new MyPromise((resolved, rejected) => {

        // 傳入要執行的回調函數
        let callBackExcu = (callback) => {
            try{
                let result = callback(this.data);
                if(result instanceof MyPromise){
                    // 如果回調返回值還是promise則then返回的promise的狀態取決於回調的返回的promise,成功就執行resolved失敗就執行rejected
                    result.then(resolved, rejected);
                }else{
                    // 如果回調的返回值不為promise則新的promise狀態為resolved
                    resolved(result)
                }
            }catch(error){
                // 如果回調執行拋處異常,則新的promise狀態為rejected
                rejected(error);
            }
        }

        if(this.status === PENDING){
            // 如果狀態為pending則保存回調且確保回調執行后能修改當前返回的promise的狀態
            this.onResolvedList.push((value) => {
                callBackExcu(onResolved)
            });
            this.onRejectedList.push((reason) => {
                callBackExcu(onRejected)
            });
        }else{
            // 如果狀態不為pending則根據狀態執行對應的回調,且修改當前promise的狀態
            switch(this.status){
                case REJECTED:
                    // onRejected異步執行
                    setTimeout(() => {
                       callBackExcu(onRejected); 
                    });
                    break;
                case RESOLVED:
                    // onResolved異步執行
                    setTimeout(() => {
                       callBackExcu(onResolved); 
                    });
                    break;
            }
        }
    });
}

catch

catch和then其實差不多,不同點在於傳入的參數只有onRejected,所以

MyPromise.prototype.catch = function(onRejected){
    // catch與then的不同點在於傳入的參數不一樣,不需要傳onResolve
    return this.then(null, onRejected);
}

Promise.resolved

  • resolved會返回一個promise對象
  • 如果傳入的參數本就是一個primise對象則直接返回
  • 如果是一個包含“then”方法的對象,返回新的promise對象,且狀態取決於then函數的執行,如果then的執行中拋錯,則新的promise狀態為rejected
  • then的參數為兩個回調函數resolved和rejected
  • 如果傳入參數value既不是promise的實例,也不是具備then函數的對象,則返回一個新的promise對象且改對象data就為value
MyPromise.resolve = function(value){
    if(value instanceof MyPromise){
        //  如果傳入的參數本就是一個primise對象則直接返回
        return value;
    }else if(typeof value.then === "function"){
        return new MyPromise((resolved, rejected) => {
            try{
                // then的參數為兩個回調函數resolved和rejected
                value.then(resolved, rejected);
            }catch(error){
                // 如果then的執行中拋錯,則新的promise狀態為rejected
                rejected(error);
            }
        });
    }else{
        // 如果傳入參數value既不是promise的實例
        return new MyPromise((resolved, rejected) => {
            resolved(value);
        });
    }
}

Promise.rejected

  • 接受參數reason,返回一個狀態為rejected的data為reason的promise實例
MyPromise.reject = function(reason){
    return new MyPromise((resolved, rejected) => {
        rejected(reason);
    });
}

Promise.all

  • 接收的參數是需要滿足可迭代協議,否則會拋錯
  • 返回值是個promise
  • 如果傳入的參數是個空的可迭代的對象,則返回一個狀態為resolved的可promise實例,data為空數組,
Promise.all([]) // Promise {<resolved>: Array(0)}
Promise.all("") // Promise {<resolved>: Array(0)}
  • 如果傳入的參數中沒有promise實例,或者所有的promise已經是resolved狀態,則返回一個promise狀態為pending,且異步更新為resolved
let p = Promise.all([1,2,3,4,Promise.resolve(5)])
console.log(p); // Promise {<pending>}
  • 如果存在promise且狀態還是pending,返回一個promise實例,且等所有promise都resolved后,狀態更新為resolved,data為傳入的順序

接下來看下源碼

// 先定義一個驗證參數是否滿足可迭代協議的方法
const isIterable = function(object){
        return typeof object[Symbol.iterator] === "function"
        && typeof object[Symbol.iterator]() === "object"
        && typeof object[Symbol.iterator]().next === "function"
}

MyPromise.all = function(iterable){
    if(!isIterable(iterable)){
        // 不滿足可迭代協議拋錯
        throw new TypeError("Object is not iterable");
    }

    let data = [];
    let count = 0;
    // 迭代參數生成數組
    let params = Array.from(iterable);

    return new MyPromise((resolved, rejected) => {
        if(params.length === 0){
            // 如果是空的可迭代對象,返回空數組
            resolved(data);
        }else{
            params.forEach((element, index) => {
                // 遍歷每個參數,統一處理成promise的實例
                // 這樣就少了一個邏輯分支
                let itemPromise = MyPromise.resolve(element);
                itemPromise.then(
                    value => {
                        // data中的結果需要和傳入參數的順序一致
                        data[index] = value;
                        if(count === params.length - 1){
                            // 說明全都resolved了
                            resolved(data);
                        }
                        count++;
                    },
                    reason => {
                        // reject直接返回
                        rejected(reason);
                    }
                );
            });
        }
    });
}

Promise.race

  • 接收一個可迭代對象,這點與方法"all"相同
  • 返回一個新的promise
  • 返回的promise狀態為pending,異步更新為resolved
let p = Promise.race([1,2,3,4]);
console.log(p); // Promise {<pending>}

p.then(
    value => {
        console.log(value); // Promise{<resolved>: 1}
    }
);
  • 傳入的若干promise中,只要有一個promise最先resolved或者rejected,則返回的promise狀態更新為resolved
let p1 = new Promise((resolved, rejected) => {
    setTimeout(() => {
        resolved("p1");
    }, 10);
});

let p2 = new Promise((resolved, rejected) => {
    setTimeout(() => {
        resolved("p2");
    }, 100);
});

let p = Promise.race([p2, p1])

p.then(
    value => {
        console.log(value); // p1
    }
);

最后來看一下自己源碼的實現

MyPromise.race = function(iterable){
    if(!isIterable(iterable)){
        // 不滿足可迭代協議拋錯
        throw new TypeError("Object is not iterable");
    }

    const params = Array.from(iterable);

    return new MyPromise((resolved, rejected) => {
        params.forEach((element, index) => {
            const itemPromise = MyPromise.resolve(element);

            itemPromise.then(
                value => {
                    // 只要有一個promise resolved直接返回
                    resolved(value);
                },
                error => {
                    // 只要有一個promise rejected直接返回
                    rejected(error);
                }
            );
        });
    });
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM