Promise 鏈式調用原理精簡示例

前言

在面試的過程中,總有一些面試官會問你,手寫一個簡易版的Promise得行不,得行的話就寫一個出來看看,啪一哈,就把紙和筆給瞭你。 我們思索半天就寫出來瞭個下面這個。 哦豁,高薪張開瞭它的翅膀,遠離瞭我們。

class Promise {
    constructor (resolve, reject) {}
    resolve () {}
    reject (){}
    then () {}
    catch () {}
    once () {}
    all () {}
    ...
}

本篇文章將不講述手寫出來一個簡易的Promise,感興趣的朋友可以去看我這篇文章 -> Promise詳解-手寫Promise,實現一款自己的簡易Promise

本篇文章記錄的是如何實現Promise的核心功能之一的.then 鏈式調用,采用構造函數的寫法,本篇文章的代碼不考慮任何容錯和異常處理,隻單獨說明其鏈式調用原理,方便理解。

先擺上完整代碼,去掉註釋和一些換行共20行有餘。

代碼

function CustomPromise (fn) {
    // 回調收集
    this.callbackList = []
    // 傳遞給Promise處理函數的resolve
    const resolve = (value) => {
        // 註意promise的then函數需要異步執行
        setTimeout(() => {
            // 這裡直接往實例上掛個data
            this.data = value;
            // 把callbackList數組裡的函數依次執行一遍
            this.callbackList.forEach(cb => cb(value))
        });
    }
    /*
        執行用戶傳入的函數 
        並且把resolve方法交給用戶執行
    */ 
    fn(resolve)
}
/*
    重點
*/
// 往構造函數的原型上掛載.then方法
CustomPromise.prototype.then = function (onReaolved) {
    // return 一個promise 實例
    return new CustomPromise((resolve) => {
        // 往回調數組中插入回調
        this.callbackList.push(()=>{
            const response = onReaolved(this.data)
            // 判斷是否是一個 CustomPromise
            if(response instanceof CustomPromise){
                // resolve 的權力被交給瞭user promise
                response.then(resolve)
            }else{
                // 如果是普通值,直接resolve
                // 依次執行callbackList裡的函數 並且把值傳遞給callbackList
                resolve(response)
            }
        })
    })
}

經典案例

    new CustomPromise((resolve) => {
        setTimeout(() => {
            // resolve1
            resolve(1);
        }, 300);
    }).then((res) => {// then1
        console.log(res);
        // 返回一個 CustomPromise 
        return new CustomPromise((resolve) => {
            setTimeout(() => {
                // resolve2
                resolve(2);
            }, 300);
        });
    }).then(res => {// then2
        console.log(res);
    });

完整的代碼和例子已奉上,現在來進行解釋。 固然結果很重要,但過程也很重要。我們要做到 知其然知其所以然。

解析

第一步

首先,我們我們先創建這樣一個Promise, 這裡需要使用匿名函數,不能使用箭頭函數,或者你可以根據這個方法已class 類的方法進行實現。

大概步驟如下:

  • 聲明構造函數/類
  • 在內部聲明一個數組名為callbackList用來裝回調,並放到this裡面
  • 聲明一個名resolve的方法,用來傳遞給Promise進行處理,註意:resolve 內部需要為異步,這裡可以采用 setTimeout 實現
  • 循環callbackList並執行裡面的方法

寫出來後的樣子長這樣:

function CustomPromise (fn) {
    // 回調收集
    this.callbackList = []
    // 傳遞給Promise處理函數的resolve
    const resolve = (value) => {
        // 註意promise的then函數需要異步執行
        setTimeout(() => {
            // 這裡直接往實例上掛個data
            this.data = value;
            // 把callbackList數組裡的函數依次執行一遍
            this.callbackList.forEach(cb => cb(value))
        });
    }
    /*
        - fn 為用戶傳進來的函數
        - 執行用戶傳入的函數 
        - 並且把resolve方法交給用戶執行
    */ 
    fn(resolve)
}

第二步

註意:第二步是本篇文章的重點,也是這個核心功能的一個重點。

我們需要往CustomPromise的原型上掛載一個.then的方法。並返回的是一個Promise實例,這裡依舊使用的是匿名函數。

完整代碼長這樣:

// 往構造函數的原型上掛載.then方法
CustomPromise.prototype.then = function (onReaolved) {
    // return 一個promise 實例
    return new CustomPromise((resolve) => {
        // 往回調數組中插入回調
        this.callbackList.push(()=>{
            const response = onReaolved(this.data)
            // 判斷是否是一個 CustomPromise
            if(response instanceof CustomPromise){
                // resolve 的權力被交給瞭user promise
                response.then(resolve)
            }else{
                // 如果是普通值,直接resolve
                // 依次執行callbackList裡的函數 並且把值傳遞給callbackList
                resolve(response)
            }
        })
    })
}

寫出來過後,在結合上面的那個例子使用,不能說和原生Promise一模一樣,但使用起來的鏈式效果卻是一毛一樣。

分析說明,此過程需結合上文中的案例一起閱讀

    const promise1 = new CustomPromise((resolve) => {
        setTimeout(() => resolve(1));
    })
    promise1.then((res) => {
        const userPromise = new CustomPromise((resolve) => {
            setTimeout(() => resolve(2), 300);
        });
        return userPromise
    });

說明:

  • 我們把new Promise返回的實例叫做promise1
  • Promise.prototype.then的實現中,我們構造瞭一個新的promise 返回,叫它promise2 在調用then方法的時候,用戶手動構造瞭一個promise並且返回,用來做異步的操作,叫它userPromise,那麼在then的實現中,內部的this其實就指向promise1promise2的傳入的fn函數執行瞭一個this.cbs.push()的操作,其實是往promise1callbackList數組中push瞭一個函數,等待後續執行
CustomPromise.prototype.then = function (onReaolved) {
    // promise 2
    return new CustomPromise((resolve) => {
        // 往回調數組中插入回調
        this.callbackList.push(()=>{})
    })
}

如果用戶傳入給thenonResolved方法返回的是個userPromise,那麼這個userPromise裡用戶會自己去在合適的時機 resolvePromise2,那麼進而這裡的response.then(resolve) 中的resolve就會被執行

if(response instanceof CustomPromise){
    response.then(resolve)
}

再結合上面的經典案例看,我這裡再放一遍

    new CustomPromise((resolve) => {
        setTimeout(() => {
            // resolve1
            resolve(1);
        }, 300);
    }).then((res) => {// then1
        console.log(res);
        // userPromise
        return new CustomPromise((resolve) => {
            setTimeout(() => {
                // resolve2
                resolve(2);
            }, 300);
        });
    }).then(res => {// then2
        console.log(res);
    });

then1這一整塊其實返回的是promise2,那麼then2 其實本質上是promise2.then(()=>{}), 也就是說then2註冊的回調函數,其實進入瞭promise2callbackList回調數組裡。 又因為我們剛剛知道,resolve2調用瞭之後,userPromise 會被resolve,進而觸發promise2resolve,進而 promise2裡的callbackList數組被依次觸發。 這樣就實現瞭用戶自己寫的resolve2執行完畢後,then2裡的邏輯才會繼續執行,也就是異步鏈式調用。

說句題外話,這個有點繞,當時還是看瞭好一會才看懂。

好瞭,當你看到這裡的時候,這篇文章已經接近尾聲瞭,是時候進行總結瞭。

總結

本篇文章隻是根據其原理實現的一個簡易鏈式調用的過程,真正的Promise並沒有這麼簡單,和上文中的比起來復雜很多,而且涉及到很多的異常、容錯、邊界等情況的處理。

最後推薦一下Promise A+規范 -> 點我查看規范,很值得去看,相信看完後會對Promise有一個更深的瞭解。

以上就是Promise 鏈式調用原理精簡示例的詳細內容,更多關於Promise 鏈式調用的資料請關註WalkonNet其它相關文章!

推薦閱讀: