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
,非同步完成後的狀態有個方便的總稱叫settled
,settled
有兩種情況,成功的狀態叫resolved
(以前也稱fullfilled
),失敗的叫rejected
。
如果不論成功還是失敗都想執行一段程式碼(也就是在settled
狀態就執行),可以使用.finally
somePromise.then( ... ).catch( ... ).finally( ... )
複製程式碼