雖然Promise是開發過程中使用非常頻繁的一個技術點,但是它的一些細節可能很多人都沒有去關注過。我們都知道,.then, .catch, .finally都可以鏈式調用,其本質上是因為返回了一個新的Promise實例,而這些Promise實例現在的狀態是什麼或者將來會變成什麼狀態,很多人心裏可能都沒個底。我自己也意識到了這一點,於是我通過一些代碼試驗,發現了一些共性。如果您對這塊內容還沒有把握,不妨看看。
閲讀本文前,您應該對Promise有一些基本認識,比如:
Promise有pending,fulfilled,rejected三種狀態,其決議函數resolve()能將Promise實例的狀態由pending轉為fulfilled,其決議函數reject()能將Promise實例的狀態由pending轉為rejected。Promise實例的狀態一旦轉變,不可再逆轉。
本文會從一些測驗代碼入手,看看Promise的幾個原型方法在處理Promise狀態時的一些細節,最後對它們進行總結歸納,加深理解!
先考慮then的行為
then的語法形式如下:
p.then(onFulfilled[, onRejected]);
onFulfilled可以接受一個value參數,作為Promise狀態決議為fulfilled的結果,onRejected可以接受一個reason參數,作為Promise狀態決議為rejected的原因。
- 如果
onFulfilled或onRejected不返回值,那麼.then返回的Promise實例的狀態會變成fulfilled,但是伴隨fulfilled的value會是undefined。
new Promise((resolve, reject) => {
resolve(1)
// reject(2)
}).then(value => {
console.log('resolution occurred, and the value is: ', value)
}, reason => {
console.log('rejection occurred, and the reason is: ', reason)
}).then(value => {
console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
- 如果
onFulfilled或onRejected返回一個值x,那麼.then返回的Promise實例的狀態會變成fulfilled,並且伴隨fulfilled的value會是x。注意,一個非Promise的普通值在被返回時會被Promise.resolve(x)包裝成為一個狀態為fulfilled的Promise實例。
new Promise((resolve, reject) => {
reject(2)
}).then(value => {
console.log('resolution occurred, and the value is: ', value)
}, reason => {
console.log('rejection occurred, and the reason is: ', reason)
return 'a new value'
}).then(value => {
console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
- 如果
onFulfilled或onRejected中拋出一個異常,那麼.then返回的Promise實例的狀態會變成rejected,並且伴隨rejected的reason是剛才拋出的異常的錯誤對象e。
new Promise((resolve, reject) => {
resolve(1)
}).then(value => {
console.log('resolution occurred, and the value is: ', value)
throw new Error('some error occurred.')
}, reason => {
console.log('rejection occurred, and the reason is: ', reason)
}).then(value => {
console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
- 如果
onFulfilled或onRejected返回一個Promise實例p2,那麼不管p2的狀態是什麼,.then返回的新Promise實例p1的狀態會取決於p2。如果p2現在或將來是fulfilled,那麼p1的狀態也隨之變成fulfilled,並且伴隨fulfilled的value也與p2進行resolve(value)決議時傳遞的value相同;
new Promise((resolve, reject) => {
resolve(1)
// reject(2)
}).then(value => {
console.log('resolution occurred, and the value is: ', value)
// return Promise.resolve('a fulfilled promise')
return Promise.reject('a rejected promise')
}, reason => {
console.log('rejection occurred, and the reason is: ', reason)
return Promise.resolve('a fulfilled promise')
// return Promise.reject('a rejected promise')
}).then(value => {
console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
這個邏輯同樣適用於rejected的場景。也就是説,如果p2的狀態現在或將來是rejected,那麼p1的狀態也隨之變成rejected,而reason也來源於p1進行reject(reason)決議時傳遞的reason。
new Promise((resolve, reject) => {
reject(1)
}).then(value => {
console.log('resolution occurred, and the value is: ', value)
}, reason => {
console.log('rejection occurred, and the reason is: ', reason)
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('a promise rejected after 3 seconds.')
}, 3000)
})
}).then(value => {
console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
再考慮catch的行為
catch的語法形式如下:
p.catch(onRejected);
.catch只會處理rejected的情況,並且也會返回一個新的Promise實例。
.catch(onRejected)與then(undefined, onRejected)在表現上是一致的。
事實上,catch(onRejected)從內部調用了then(undefined, onRejected)。
- 如果
.catch(onRejected)的onRejected回調中返回了一個狀態為rejected的Promise實例,那麼.catch返回的Promise實例的狀態也將變成rejected。
new Promise((resolve, reject) => {
reject(1)
}).catch(reason => {
console.log('rejection occurred, and the reason is: ', reason)
return Promise.reject('rejected')
}).then(value => {
console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
- 如果
.catch(onRejected)的onRejected回調中拋出了異常,那麼.catch返回的Promise實例的狀態也將變成rejected。
new Promise((resolve, reject) => {
reject(1)
}).catch(reason => {
console.log('rejection occurred, and the reason is: ', reason)
throw 2
}).then(value => {
console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
- 其他情況下,
.catch返回的Promise實例的狀態將是fulfilled。
then, catch 小結
綜合以上來看,不管是.then(onFulfilled, onRejected),還是.catch(onRejected),它們返回的Promise實例的狀態都取決於回調函數是否拋出異常,以及返回值是什麼。
- 如果回調函數的返回值是一個狀態為
rejected的Promise實例,那麼.then,.catch返回的Promise實例的狀態就是rejected。 - 如果回調函數的返回值是一個還未決議的
Promise實例p2,那麼.then,.catch返回的Promise實例p1的狀態取決於p2的決議結果。 - 如果回調函數中拋出了異常,那麼
.then,.catch返回的Promise實例的狀態就是rejected,並且reason是所拋出異常的對象e。 -
其他情況下,
.then,.catch返回的Promise實例的狀態將是fulfilled。最後看看finally
不管一個Promise的狀態是fulfilled還是rejected,傳遞到finally方法的回調函數onFinally都會被執行。我們可以把一些公共行為放在onFinally執行,比如把loading狀態置為false。
注意,onFinally不會接受任何參數,因為它從設計上並不關心Promise實例的狀態是什麼。
p.finally(function() {
// settled (fulfilled or rejected)
});
finally方法也會返回一個新的Promise實例,這個新的Promise實例的狀態也取決於onFinally的返回值是什麼,以及onFinally中是否拋出異常。
你可以通過修改以下代碼中的註釋部分來驗證,不同的返回值對於finally返回的Promise實例的狀態的影響。
new Promise((resolve, reject) => {
reject(1)
}).then(value => {
console.log('resolution occurred, and the value is: ', value)
}, reason => {
console.log('rejection occurred, and the reason is: ', reason)
return Promise.resolve(2);
// return Promise.reject(3)
}).finally(() => {
// return Promise.resolve(4)
// return Promise.reject(5)
throw new Error('an error')
}).then(value => {
console.log('resolution of the returned promise occurred, and the value is: ', value)
}, reason => {
console.log('rejection of the returned promise occurred, and the reason is: ', reason)
})
經過測試,可以發現,不管當前Promise的狀態是fulfilled還是rejected,只要在onFinally中沒有發生以下任何一條情況,finally方法返回的新的Promise實例的狀態就會與當前Promise的狀態保持一致!這也意味着即使在onFinally中返回一個狀態為fulfilled的Promise也不能阻止新的Promise實例採納當前Promise的狀態或值!
- 返回一個狀態為或將為
rejected的Promise - 拋出錯誤
總的來説,在finally情況下,rejected優先!
如何理解then中拋出異常後會觸發隨後的catch
由於.then會返回一個新的Promise實例,而在.then回調中拋出了異常,導致這個新Promise的狀態變成了rejected,而.catch正是用於處理這個新的Promise實例的rejected場景的。
new Promise((resolve, reject) => {
resolve(1)
}).then(value => {
console.log('resolution of the returned promise occurred, and the value is: ', value)
var a = b; // 未定義b
}).catch(reason => {
console.log('caught the error occured in the callback of then method, and the reason is: ', reason)
})
最關鍵一點就是要理解:每次.then, .catch, .finally都產生一個新的Promise實例。
Promise和jQuery的鏈式調用區別在哪?
上文也提到了,.then, .catch, .finally都產生一個新的Promise實例,所以這種鏈式調用的對象實例已經發生了變化。可以理解為:
Promise.prototype.then = function() {
// balabala
return new Promise((resolve, reject) => {
// if balabala
// else if balabala
// else balabala
});
}
而jQuery鏈式調用是基於同一個jQuery實例的,可以簡單表述為:
jQuery.fn.css = function() {
// balabala
return this;
}
感謝閲讀
本文主要是參考了MDN和《你不知道的JavaScript(下卷)》上關於Promise的知識點,簡單分析了.then, .catch, .finally中回調函數的不同行為對於三者返回的Promise實例的影響,希望對大家有所幫助。
- 收藏吃灰不如現在就開始學習,奧利給!
- 如果您覺得本文有所幫助,請留下您的點贊關注支持一波,謝謝!
- 快關注公眾號程序員白彬,與筆者一起交流學習吧!