通過程式碼範例帶你瞭解Promise

2023-03-01 22:00:13

本篇文章通過多段程式碼範例帶大家瞭解 Promise 的基礎用法,以及更進一步掌握 Promise 非同步存取的思想。

之前一直有聽說 Promise 的威名,但是總覺得是個較為深奧的東西,有點畏難而沒能真正地去了解。最近看了李立超老師在B站傳的 Node.js 的視訊,感覺講的很清晰,自己在這做進一步的梳理。

先來看一個問題

我們都知道 JavaScript 是單執行緒執行的,所以如果遇到一個資料需要過段時間才能獲取到的情況,就會形成阻塞導致後面的程式碼也無法執行,而這相當致命,比如下面程式碼

function sum(a, b) {
    const begin = Date.now();
    while(Date.now() - begin < 10000) {

    }
    return a+b;
}

console.log(sum(1,2));
console.log("1");
登入後複製

中間的 while 語句經歷了10秒的迴圈,最終才分別列印出了 3 和 1

然而我們希望的是允許3在10秒後再列印出來,但是1得先列印出來

這裡我們就用到了setTimeout,修改程式碼如下

function sum(a, b) {
    setTimeout(() => {
        return a+b;
    },10000)
}

console.log(sum(1,2));
console.log("1");
登入後複製

執行一下可以看到1確實瞬間被列印出來了,但是本該列印3的位置是undefined

image.png

原因在於此時的console.log同樣沒有等待setTimeout走完,無法接收到10秒後的資料

所以為了能夠接收到這個10秒後的資料,我們可以採用回撥函數的方式

function sum(a, b, callback) {

    setTimeout(() =>{
        callback(a+b);
    }, 10000)

}

sum(1,2,(result) => {
    console.log(result);
});
console.log("1");
登入後複製

傳入了一個能夠接收 a+b 為引數的回撥函數 (result) => {console.log(result);}

所以在10秒後會執行這個回撥函數,進行列印,結果如下

image.png

這樣我們就初步解決了這個問題,一個需要延時獲取的資料在其他程式碼先執行後再被獲取。

然而 Promise 還沒出現,這就涉及了另一個需要改進的地方

回撥地獄

這是個乍一聽很唬人的稱呼,實際上就是多層回撥函數的巢狀導致的不利於閱讀和偵錯的情況。

比如此時我們想要多次呼叫這個sum函數,要在得到1+2的結果後,再獲得 1+2+3,1+2+3+4 這些結果

所以我們得在sum傳入的回撥函數裡再多次呼叫sum進行巢狀,如下

sum(1,2,(result) => {
    sum(result, 3, (result) => {
        sum(result, 4, (result) => {
            console.log(result);
        })
    })
});
登入後複製

image.png

這種類似金字塔的結構可讀性差且不好偵錯,被稱作回撥地獄。

所以此時終於到了Promise出場的時候,它的出現解決了回撥地獄的問題。

Promise 是什麼

在使用Promise解決回撥地獄的問題前,先來大致地瞭解一下什麼是Promise。

目前我對它的判斷是,Promise 是一個用於存取非同步資料的物件。

首先來看一下空的 Promise 列印出來會是什麼

const promise = new Promise(()=>{});
登入後複製

image.png

其中最關鍵的就是 PromiseState 和 PromiseResult 兩個值,之後會詳細展開,這裡只要知道Promise中有著這兩個屬性即可。

接著來看一下 promise 存資料的過程,最關鍵的就是要知道有 resolve 和 reject,比如下面程式碼

const promise = new Promise((resolve, reject) => {
    const flag = true;
    if (flag) {
        resolve("resolve datas");
    } else {
        reject("reject data");
    }
})
登入後複製

此時flag為true,所以執行的是resolve的儲存,得到的結果如下

image.png

而當我們把flag改為false,執行reject的儲存時,得到的結果如下

image.png

現在是解釋上面兩個屬性的時候了,

  • 當 promise 沒有儲存資料時,PromiseState 的值為 pending,PromiseResult的值為 undefined
  • 當 promise 使用 resolve 儲存資料時,PromiseState 的值為 pending,PromiseResult的值為 相應儲存值
  • 當 promise 使用 reject 儲存資料時,PromiseState 的值為 rejected,PromiseResult的值為相應儲存值

既然存有兩種型別,讀自然也要分兩種

當我們讀取promise中的資料時,我們需要使用如下的結構

promise.then(result => {
    console.log(result);
}, reason => {
    console.log(reason);
})
登入後複製

如果資料存在resolve中,result會返回結果,如果存在reject中,reason會返回結果。

使用Promise解決回撥地獄

在初步瞭解了Promise後,會發現目前Promise能做的事,使用回撥函數也能完成。

所以最主要的還是Promise解決了回撥地獄,比如之前的問題,可以寫成這種形式

function sum(a, b) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(a+b);
        }, 1000);
    })
}

sum(1,2)
    .then(result => sum(result,3))
    .then(result => sum(result,4))
    .then(result => {
        console.log(result);
    })
登入後複製

promise 通過then方法進行讀取後,是個新的Promise物件,比如我們可以列印一下

function sum(a, b) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(a+b);
        }, 1000);
    })
}

console.log(sum(1,2)
    .then(result => sum(result,3)))
登入後複製

image.png

所以這也就給了我們能多次呼叫then方法的基礎。

而這也就解決了回撥地獄的問題。

小結

Promise 是一個可以存取非同步資料的物件,通過resolvereject來儲存資料,可以通過then來讀取資料

至於其他的.catch .finally .race .any .all 這些方法就不再多作贅述,詳細的見檔案

【推薦學習:】

以上就是通過程式碼範例帶你瞭解Promise的詳細內容,更多請關注TW511.COM其它相關文章!