溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

分析Promise鏈?zhǔn)秸{(diào)用

發(fā)布時間:2021-11-04 15:45:34 來源:億速云 閱讀:121 作者:iii 欄目:web開發(fā)

本篇內(nèi)容介紹了“分析Promise鏈?zhǔn)秸{(diào)用”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

一、前言

上一節(jié)中,實(shí)現(xiàn)了 Promise 的基礎(chǔ)版本:

//極簡的實(shí)現(xiàn)+鏈?zhǔn)秸{(diào)用+延遲機(jī)制+狀態(tài)
class Promise {
    callbacks = [];
    state = 'pending';//增加狀態(tài)
    value = null;//保存結(jié)果
    constructor(fn) {
        fn(this._resolve.bind(this));
    }
    then(onFulfilled) {
        if (this.state === 'pending') {//在resolve之前,跟之前邏輯一樣,添加到callbacks中
            this.callbacks.push(onFulfilled);
        } else {//在resolve之后,直接執(zhí)行回調(diào),返回結(jié)果了
            onFulfilled(this.value);
        }
        return this;
    }
    _resolve(value) {
        this.state = 'fulfilled';//改變狀態(tài)
        this.value = value;//保存結(jié)果
        this.callbacks.forEach(fn => fn(value));
    }
}

但鏈?zhǔn)秸{(diào)用,只是在 then 方法中 return 了 this,使得 Promise 實(shí)例可以多次調(diào)用 then 方法,但因?yàn)槭峭粋€實(shí)例,調(diào)用再多次 then 也只能返回相同的一個結(jié)果,通常我們希望的鏈?zhǔn)秸{(diào)用是這樣的:

//使用Promise
function getUserId(url) {
    return new Promise(function (resolve) {
        //異步請求
        http.get(url, function (id) {
            resolve(id)
        })
    })
}
getUserId('some_url').then(function (id) {
    //do something
    return getNameById(id);
}).then(function (name) {
    //do something
    return getCourseByName(name);
}).then(function (course) {
    //do something
    return getCourseDetailByCourse(course);
}).then(function (courseDetail) {
    //do something
});

每個 then 注冊的 onFulfilled 都返回了不同的結(jié)果,層層遞進(jìn),很明顯在 then 方法中 return this 不能達(dá)到這個效果。引入真正的鏈?zhǔn)秸{(diào)用,then 返回的一定是一個新的Promise實(shí)例。

分析Promise鏈?zhǔn)秸{(diào)用

真正的鏈?zhǔn)?Promise 是指在當(dāng)前 Promise 達(dá)到 fulfilled 狀態(tài)后,即開始進(jìn)行下一個 Promise(后鄰 Promise)。那么我們?nèi)绾毋暯赢?dāng)前 Promise 和后鄰 Promise 呢?(這是理解 Promise 的難點(diǎn),我們會通過動畫演示這個過程)。

二、鏈?zhǔn)秸{(diào)用的實(shí)現(xiàn)

先看下實(shí)現(xiàn)源碼:

//完整的實(shí)現(xiàn)
class Promise {
    callbacks = [];
    state = 'pending';//增加狀態(tài)
    value = null;//保存結(jié)果
    constructor(fn) {
        fn(this._resolve.bind(this));
    }
    then(onFulfilled) {
        return new Promise(resolve => {
            this._handle({
                onFulfilled: onFulfilled || null,
                resolve: resolve
            });
        });
    }
    _handle(callback) {
        if (this.state === 'pending') {
            this.callbacks.push(callback);
            return;
        }
        //如果then中沒有傳遞任何東西
        if (!callback.onFulfilled) {
            callback.resolve(this.value);
            return;
        }
        var ret = callback.onFulfilled(this.value);
        callback.resolve(ret);
    }
    _resolve(value) {
        this.state = 'fulfilled';//改變狀態(tài)
        this.value = value;//保存結(jié)果
        this.callbacks.forEach(callback => this._handle(callback));
    }
}

由上面的實(shí)現(xiàn),我們可以看到:

  • then 方法中,創(chuàng)建并返回了新的 Promise 實(shí)例,這是串行Promise的基礎(chǔ),是實(shí)現(xiàn)真正鏈?zhǔn)秸{(diào)用的根本。

  • then 方法傳入的形參 onFulfilled 以及創(chuàng)建新 Promise 實(shí)例時傳入的 resolve 放在一起,被push到當(dāng)前 Promise 的 callbacks 隊列中,這是銜接當(dāng)前 Promise 和后鄰 Promise 的關(guān)鍵所在。

  • 根據(jù)規(guī)范,onFulfilled 是可以為空的,為空時不調(diào)用 onFulfilled。

看下動畫演示:

分析Promise鏈?zhǔn)秸{(diào)用

(Promise 鏈?zhǔn)秸{(diào)用演示動畫)

當(dāng)?shù)谝粋€ Promise 成功時,resolve 方法將其狀態(tài)置為 fulfilled ,并保存 resolve 帶過來的value。然后取出 callbacks 中的對象,執(zhí)行當(dāng)前 Promise的 onFulfilled,返回值通過調(diào)用第二個 Promise 的 resolve 方法,傳遞給第二個 Promise。動畫演示如下:

分析Promise鏈?zhǔn)秸{(diào)用

(Promise 鏈?zhǔn)秸{(diào)用 fulfilled)

為了真實(shí)的看到鏈?zhǔn)秸{(diào)用的過程,我寫一個mockAjax函數(shù),用來模擬異步請求:

/**
 * 模擬異步請求
 * @param {*} url  請求的URL
 * @param {*} s  指定該請求的耗時,即多久之后請求會返回。單位秒
 * @param {*} callback 請求返回后的回調(diào)函數(shù)
 */
const mockAjax = (url, s, callback) => {
    setTimeout(() => {
        callback(url + '異步請求耗時' + s + '秒');
    }, 1000 * s)
}

除此之外,我給 Promise 的源碼加上了日志輸出并增加了構(gòu)造順序標(biāo)識,可以清楚的看到構(gòu)造以及執(zhí)行過程:

//Demo1
new Promise(resolve => {
  mockAjax('getUserId', 1, function (result) {
    resolve(result);
  })
}).then(result => {
  console.log(result);
})

【 Demo1 的源碼】

執(zhí)行結(jié)果如下:

[Promse-1]:constructor
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId異步請求耗時1秒
[Promse-1]:_handle state= fulfilled
getUserId異步請求耗時1秒
[Promse-2]:_resolve
[Promse-2]:_resolve value= undefined

通過打印出來的日志,可以看到:

  1. 構(gòu)造 Promise-1 實(shí)例,立即執(zhí)行 mackAjax('getUserId',callback);

  2. 調(diào)用 Promise-1 的 then 方法,注冊 Promise-1 的 onFulfilled 函數(shù)。

  3. then 函數(shù)內(nèi)部構(gòu)造了一個新的 Promise實(shí)例:Promise-2。立即執(zhí)行 Promise-1 的 _handle方法。

  4. 此時 Promise-1 還是pending的狀態(tài)。

  5. Promise-1._handle 中就把注冊在 Promise-1 的 onFulfilled 和 Promise-2 的 resolve 保存在 Promise-1 內(nèi)部的 callbacks。

  6. 至此當(dāng)前線程執(zhí)行結(jié)束。返回的是 Promise-2 的 Promise實(shí)例。

  7. 1s后,異步請求返回,要改變 Promise-1 的狀態(tài)和結(jié)果,執(zhí)行 resolve(result)。

  8. Promise-1 的值被改變,內(nèi)容為異步請求返回的結(jié)果:"getUserId異步請求耗時1s"。

  9. Promise-1 的狀態(tài)變成 fulfilled。

  10. Promise-1 的 onFulfilled 被執(zhí)行,打印出了"getUserId異步請求耗時1秒"。

  11. 然后再調(diào)用 Promise-2.resolve。

  12. 改變 Promise-2 的值和狀態(tài),因?yàn)?Promise-1 的 onFulfilled 沒有返回值,所以 Promise-2的值為undefined。

上例中,如果把異步的請求改成同步會是什么的效果?

new Promise(resolve => {
  resolve('getUserId同步請求');
}).then(result => {
    console.log(result);
});
//打印日志
[Promse-1]:constructor
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId同步請求
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= fulfilled
getUserId同步請求
[Promse-2]:_resolve
[Promse-2]:_resolve value= undefined
=> Promise {
  callbacks: [],
  name: 'Promse-2',
  state: 'fulfilled',
  value: undefined }

感興趣的可以自己去分析一下。

三、鏈?zhǔn)秸{(diào)用真正的意義

執(zhí)行當(dāng)前 Promise 的 onFulfilled 時,返回值通過調(diào)用第二個 Promise 的 resolve 方法,傳遞給第二個 Promise,作為第二個 Promise 的值。于是我們考慮如下Demo:

//Demo2
new Promise(resolve => {
    mockAjax('getUserId', 1, function (result) {
        resolve(result);
    })
}).then(result => {
    console.log(result);
    //對result進(jìn)行第一層加工
    let exResult = '前綴:' + result;
    return exResult;
}).then(exResult => {
    console.log(exResult);
});

【 Demo2 的源碼】

我們加了一層 then,來看下執(zhí)行的結(jié)果:

[Promse-1]:constructor
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-2]:then
[Promse-3]:constructor
[Promse-2]:_handle state= pending
[Promse-2]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-3', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId異步請求耗時1秒
[Promse-1]:_handle state= fulfilled
getUserId異步請求耗時1秒
[Promse-2]:_resolve
[Promse-2]:_resolve value= 前綴:getUserId異步請求耗時1秒
[Promse-2]:_handle state= fulfilled
前綴:getUserId異步請求耗時1秒
[Promse-3]:_resolve
[Promse-3]:_resolve value= undefined:

鏈?zhǔn)秸{(diào)用可以無限的寫下去,上一級 onFulfilled return 的值,會變成下一級 onFulfilled 的結(jié)果。可以參考Demo3:

【 Demo3 的源碼】

我們很容易發(fā)現(xiàn),上述 Demo3 中只有第一個是異步請求,后面都是同步的,我們完全沒有必要這么鏈?zhǔn)降膶?shí)現(xiàn)。如下一樣能得到我們想要的三個結(jié)果: 分別打印出來的值。

//等價于 Demo3
new Promise(resolve => {
    mockAjax('getUserId', 1, function (result) {
        resolve(result);
    })
}).then(result => {
    console.log(result);
    //對result進(jìn)行第一層加工
    let exResult = '前綴:' + result;
    console.log(exResult);
    let finalResult = exResult + ':后綴';
    console.log(finalResult);
});

那鏈?zhǔn)秸{(diào)用真正的意義在哪里呢?

剛才演示的都是 onFulfilled 返回值是 value 的情況,如果是一個 Promise 呢?是不是就可以通過 onFulfilled,由使用 Promise 的開發(fā)者決定后續(xù) Promise 的狀態(tài)。

于是在 _resolve 中增加對前一個 Promise onFulfilled 返回值的判斷:

_resolve(value) {
        if (value && (typeof value === 'object' || typeof value === 'function')) {
            var then = value.then;
            if (typeof then === 'function') {
                then.call(value, this._resolve.bind(this));
                return;
            }
        }
        this.state = 'fulfilled';//改變狀態(tài)
        this.value = value;//保存結(jié)果
        this.callbacks.forEach(callback => this._handle(callback));
    }

從代碼上看,它是對 resolve 中的值作了一個特殊的判斷,判斷 resolve 的值是否為 Promise實(shí)例,如果是 Promise 實(shí)例,那么就把當(dāng)前 Promise 實(shí)例的狀態(tài)改變接口重新注冊到 resolve 的值對應(yīng)的 Promise 的 onFulfilled 中,也就是說當(dāng)前 Promise 實(shí)例的狀態(tài)要依賴 resolve 的值的 Promise 實(shí)例的狀態(tài)。

分析Promise鏈?zhǔn)秸{(diào)用

【 Demo 4 的源碼】

執(zhí)行的結(jié)果如下:

[Promse-1]:constructor
[Promse-2]:constructor
[Promse-1]:then
[Promse-3]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-3]:then
[Promse-4]:constructor
[Promse-3]:_handle state= pending
[Promse-3]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-4', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId異步請求耗時1秒
[Promse-1]:_handle state= fulfilled
getUserId異步請求耗時1秒
[Promse-3]:_resolve
[Promse-3]:_resolve value= Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-2]:then
[Promse-5]:constructor
[Promse-2]:_handle state= pending
[Promse-2]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-2]:_resolve
[Promse-2]:_resolve value= getUserName異步請求耗時2秒
[Promse-2]:_handle state= fulfilled
[Promse-3]:_resolve
[Promse-3]:_resolve value= getUserName異步請求耗時2秒
[Promse-3]:_handle state= fulfilled
getUserName異步請求耗時2秒
[Promse-4]:_resolve
[Promse-4]:_resolve value= undefined
[Promse-5]:_resolve
[Promse-5]:_resolve value= undefined

一樣的,我做了一個演示動畫,還原了這個過程:

分析Promise鏈?zhǔn)秸{(diào)用

(Promise 真正的鏈?zhǔn)秸{(diào)用)

“分析Promise鏈?zhǔn)秸{(diào)用”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI