Promise 的誤區

FateRiddle發表於2018-08-02

Essentially, a promise is a returned object to which you attach callbacks, instead of passing callbacks into a function.

一直在用Promise,但一直感覺自己沒有真正徹底弄明白Promise,總差那麼一點。 最後發現原因很簡單,是我腦內對 Promise 建立的模型錯誤了。於是寫一篇短文。

誤區

const a = new Promise(resolve => {
    const random = Math.random() > 0.5 ? 1:0
    setTimeout(() => resolve(random), 1000)
})
複製程式碼

這是一個一秒後會返回0或者1的promise。在瀏覽器的 console 裡試驗:

> a
< Promise {<resolved>: 1} 
> a
< Promise {<resolved>: 1} 
> a
< Promise {<resolved>: 1} 
> a
< Promise {<resolved>: 1} 
複製程式碼

直到地老天荒,返回的永遠是1。(當然小夥伴們試驗的話,也有50%可能會出現永遠是0的情況,總之resolved的值不會變,謝謝回覆指正)

錯誤的腦內模型

事實上,雖然知道 promise 是一個物件,但腦內並沒有接受

> typeof a
< "object"
複製程式碼

在我腦內的模型,promise 是一個"函式",先定義它,然後再呼叫它來真正使用

const b = 定義promise
b.then( 使用promise )
複製程式碼

但一個Promise在定義的時候就執行了! 以第一例的promise來說,在定義了a之後

// 一秒以內
a = { status: "pending", value: undefined }
// 一秒之後
a = { status: "resolved", value: 1 }
// 這裡用了簡化的模型來說明,事實上並不能使用 a.status 或者 a.value 來取值
複製程式碼

所以雖然在定義時並不知道最後的value會是1還是0,然而一旦定義了value就已經確定下來了,不會再變化了。

為啥會有錯誤的腦內模型?

因為正常的使用場景,我們使用的並不是 Promise,而是return Promise的函式!

fetch(url).then( ... ) // fetch 是一個返回promise的函式
readFile(url).then( ... ) //readFile 也是一個返回promise的函式
複製程式碼

因為Promise在定義時就會執行,所以正常的使用方式確實是在使用時現場定義!於是就會使用函式來包裹。再以第一例來說,要讓promise返回值在0和1之間隨機變化,只要用函式就行了:

const createRandom = () => {
    const random = Math.random() > 0.5 ? 1:0
    return new Promise(resolve => setTimeout(resolve, 1000, random))  
    // 今天剛發現setTimeout可以接受第三個引數,簡化寫法
}

// 執行
createRandom().then(r => console.log(r))  // 0
createRandom().then(r => console.log(r))  // 1
createRandom().then(r => console.log(r))  // 0
createRandom().then(r => console.log(r))  // 0
createRandom().then(r => console.log(r))  // 1
複製程式碼

容易糊塗的地方

還有一個在學習時感覺糊塗的地方 就是各種名稱,比如 resolve,reject,pending, fullfilled, respond, error 啥的,其實把定義和使用分開,一切還比較清晰

// 定義
const some = () => new Promise((resolve, reject) => {
    ... 非同步操作
    ... resolve(返回值)
    ... reject("reject的原因")
})
// 使用
some.then( 
    response => { 
        // ... 處理成功返回的 response,也就是定義時 resolve 的值
    }, 
    error => {
        // ... 處理 error, 也就是定義時 reject 的值
    }
)
複製程式碼

常用的方法

寫mock的時候一定會寫一個 delay 函式

const delay = ms => new Promise(r => setTimeout(r, ms))

delay(1000).then( ... )
複製程式碼

Promise.resolve / Promise.reject

寫mock時使用

const success = Promise.resolve(1) // 返回1的promise
const fail = Promise.reject("connection error") // rejected promise
複製程式碼

注意可以將一個promise 作為引數傳入 Promise.resolve(), 其結果會返回promise的執行結果,也就是說在不確定一個 value 是否是 promise 時,可以安全使用

Promise.resolve(value).then( ... )
複製程式碼

Promise.all / Promise.race

當你希望在多個fetch request全完成時執行操作,Promise 提供了方法

Promise.all([fetch1, fetch2, fetch3]).then( ... ) 
複製程式碼

有少數情況需要返回一組請求裡第一個返回的(不論成功失敗),可以使用

Promise.race([fetch1, fetch2, fetch3]).then( ... ) 
複製程式碼

Promise.prototype.finally

Promise的狀態:
期初是pending,非同步完成後的狀態有個方便的總稱叫settledsettled有兩種情況,成功的狀態叫resolved(以前也稱fullfilled),失敗的叫rejected

如果不論成功還是失敗都想執行一段程式碼(也就是在settled狀態就執行),可以使用.finally

somePromise.then( ... ).catch( ... ).finally( ... )
複製程式碼

相關文章