JavaScript?Promise實(shí)例方法then()、catch()、finally()詳細(xì)解析
適合有JavaScript基礎(chǔ),希望深入異步編程的開(kāi)發(fā)者。我們將用最少的篇幅,講透最核心的知識(shí)。
引言:Promise實(shí)例方法
在之前的文章中,我們探討了Promise的狀態(tài)機(jī)模型和不可變?cè)瓌t。理解了Promise的內(nèi)在狀態(tài)后,現(xiàn)在讓我們轉(zhuǎn)向它的外在行為——實(shí)例方法。如果說(shuō)狀態(tài)是Promise的"骨骼",那么實(shí)例方法就是它的"肌肉",賦予了Promise處理異步操作的能力。
我們可以把Promise就像一臺(tái)自動(dòng)咖啡機(jī)。狀態(tài)(pending、fulfilled、rejected)是它的指示燈,而then()、catch()、finally()方法就是它的操作按鈕。只有了解每個(gè)按鈕的確切功能和使用時(shí)機(jī),我們才能制作出一杯完美的"異步咖啡"。
then()方法:鏈?zhǔn)秸{(diào)用的核心
then()的基本語(yǔ)法
Then()方法是為Promise對(duì)象添加處理程序的主要方法,也是Promise中最核心、最復(fù)雜的方法。它可以接受兩個(gè)參數(shù):onResolved處理函數(shù) 和 onRejected處理函數(shù) 。這兩個(gè)參數(shù)都是可選的,如果提供的話(huà),則會(huì)在Promise進(jìn)入 fulfilled 和 rejected 狀態(tài)時(shí)執(zhí)行,如以下示例:
function onResolved(id) {
console.log("resolved:" + id)
}
function onRejected(id) {
console.log("rejected:" + id)
}
let p1 = new Promise((resolve, reject) => resolve())
let p2 = new Promise((resolve, reject) => reject())
p1.then(() => onResolved('p1'),
() => onRejected('p1'))
p2.then(() => onResolved('p2'),
() => onRejected('p2'))
上述代碼中,由于Promise只能轉(zhuǎn)換一次狀態(tài),所以p1和p2這兩個(gè)操作一定是互斥的,其結(jié)果為:
resolved:p1 rejected:p2
由于onResolved處理函數(shù) 和 onRejected處理函數(shù) 這兩個(gè)參數(shù)都是可選的,當(dāng)我們只想提供 onRejected處理函數(shù) 時(shí),可以在 onResolved 的位置上傳入undefined或null,這樣可以避免在內(nèi)存中創(chuàng)建多余的對(duì)象,因此then()方法在實(shí)際使用中,其傳參方式有三種:
promise.then(onFulfilled) // 只處理成功 promise.then(null, onRejected) // 只處理失?。ú煌扑]) promise.then(onFulfilled, onRejected) // 同時(shí)處理成功和失敗
注:
- then()方法中,也可以傳遞一個(gè)非函數(shù),如:promise.then(’Hello‘),但會(huì)被靜默忽略,因此不推薦這種寫(xiě)法。
- promise.then(null, onRejected)這種寫(xiě)法也是不推薦的,因?yàn)橐话隳J(rèn)要處理成功回調(diào)。
then()的鏈?zhǔn)秸{(diào)用
Promise真正的威力來(lái)自于then()的鏈?zhǔn)秸{(diào)用能力,這不僅僅是語(yǔ)法糖,而是一種函數(shù)式編程范式的體現(xiàn)。我們來(lái)看看下面一個(gè)例子:
// 一個(gè)典型的鏈?zhǔn)秸{(diào)用示例
fetchUserData(userId)
.then(validateUser) // 1. 驗(yàn)證用戶(hù)數(shù)據(jù)
.then(enrichWithProfile) // 2. 豐富個(gè)人信息
.then(saveToDatabase) // 3. 保存到數(shù)據(jù)庫(kù)
.then(notifySubscribers) // 4. 通知訂閱者
.then(updateCache) // 5. 更新緩存
.then(finalizeOperation); // 6. 完成操作
鏈?zhǔn)秸{(diào)用的四種返回值模式
返回普通值
當(dāng)then()方法中返回一個(gè)普通值時(shí),該值會(huì)被包裝為一個(gè)新的Promise對(duì)象,如以下示例:
const myPromise = new Promise((resolve, reject) => {
resolve('Hello');
})
myPromise.then(value => {
console.log('收到:', value);
return '新的普通值'; // 自動(dòng)包裝為 Promise.resolve('新的普通值')
}).then(newValue => {
console.log('鏈?zhǔn)絺鬟f:', newValue); // '新的普通值'
});
返回Promise
當(dāng)then()方法中返回一個(gè)Promise對(duì)象時(shí),會(huì)等待該P(yáng)romise完成后,繼續(xù)下一個(gè)then()方法的鏈?zhǔn)秸{(diào)用,如以下示例:
const myPromise = new Promise((resolve, reject) => {
resolve('Hello')
})
myPromise.then(value => {
return new Promise((resolve, reject) => {
resolve(value + ' World')
}) // 返回新的Promise
}).then(response => {
// 等待new Promise完成
console.log('處理后的結(jié)果:', response) // Hello World
});
拋出異常
當(dāng)then()方法中拋出異常時(shí),會(huì)轉(zhuǎn)換為一個(gè)rejected的Promise對(duì)象,如以下示例:
const myPromise = new Promise((resolve, reject) => {
resolve('Hello')
})
myPromise.then(value => {
if (!value.valid) {
throw new Error('數(shù)據(jù)無(wú)效') // 等價(jià)于 Promise.reject(new Error('數(shù)據(jù)無(wú)效'))
}
return processValue(value)
}).catch(error => {
console.log('捕獲到錯(cuò)誤:', error.message) // '數(shù)據(jù)無(wú)效'
})
沒(méi)有返回或返回undefined
當(dāng)then()方法中沒(méi)有返回值,或返回值為undefined時(shí),會(huì)將undefined作為一個(gè)值,繼續(xù)傳遞,如以下示例:
const myPromise = new Promise((resolve, reject) => {
resolve('Hello')
})
myPromise.then(value => {
console.log('處理值:', value)
// 沒(méi)有return語(yǔ)句
}).then(nextValue => {
console.log('下一個(gè)值:', nextValue) // undefined
})
catch()方法:錯(cuò)誤處理
catch()的本質(zhì)
Catch() 用于給Promise添加拒絕處理程序,它只接受一個(gè)參數(shù):onRejected處理函數(shù)。實(shí)際上,catch()方法是一個(gè)語(yǔ)法糖,相當(dāng)于then(null, onRejected)的語(yǔ)法,它的出現(xiàn)極大地提高了代碼的可讀性。我們來(lái)看看下面一個(gè)例子:
// 以下兩種寫(xiě)法完全等價(jià) promise.catch(onRejected) promise.then(null, onRejected) // 但.catch()的可讀性更好 fetchData() .then(process) .catch(handleError) // 清晰:這里處理錯(cuò)誤 .then(continueAfterError) // 對(duì)比:使用.then()的第二個(gè)參數(shù) fetchData() .then(process, handleError) // 不清晰:是處理fetch錯(cuò)誤還是process錯(cuò)誤? .then(continueAfterError)
錯(cuò)誤傳播機(jī)制
Promise的錯(cuò)誤處理遵循冒泡原則:錯(cuò)誤會(huì)沿著Promise鏈向后傳遞,直到被捕獲,如以下示例:
// 錯(cuò)誤傳播示例
Promise.resolve()
.then(() => {
console.log('步驟1: 成功')
return '第一步結(jié)果'
})
.then(result => {
console.log('步驟2: 收到', result)
throw new Error('步驟2發(fā)生錯(cuò)誤') // 拋出錯(cuò)誤
})
.then(result => {
console.log('步驟3: 這行不會(huì)執(zhí)行') // 被跳過(guò)
return '第三步結(jié)果';
})
.catch(error => {
console.log('捕獲到錯(cuò)誤:', error.message) // '步驟2發(fā)生錯(cuò)誤'
return '從錯(cuò)誤中恢復(fù)';
})
.then(recovery => {
console.log('恢復(fù)后繼續(xù):', recovery) // '從錯(cuò)誤中恢復(fù)'
});
finally()方法:資源清理
finally()方法的獨(dú)特性
Finally()方法用于給Promise實(shí)例添加 onFinally處理程序,這個(gè)方法可以避免onResolved處理函數(shù) 和 onRejected處理函數(shù) 中出現(xiàn)冗余代碼,無(wú)論P(yáng)romise狀態(tài)為 fulfilled 或 rejected 的都會(huì)執(zhí)行,即無(wú)論P(yáng)romise是成功還是失敗,它都會(huì)執(zhí)行。我們來(lái)看看下面的例子:
// .finally()的基本使用
fetchData()
.then(data => {
console.log('數(shù)據(jù)處理:', data);
return process(data);
})
.catch(error => {
console.error('處理失敗:', error);
throw error; // 重新拋出,讓外部知道失敗
})
.finally(() => {
console.log('清理資源'); // 無(wú)論成功失敗都會(huì)執(zhí)行
cleanupResources();
});
finally()方法的三個(gè)特性
不接受參數(shù)
promise.finally(() => {
// 這里無(wú)法訪問(wèn)Promise的結(jié)果或錯(cuò)誤原因
console.log('執(zhí)行清理')
})
原樣傳遞上游的結(jié)果或錯(cuò)誤
Promise.resolve('成功數(shù)據(jù)')
.finally(() => {
console.log('finally執(zhí)行');
// 這里返回的值不會(huì)影響鏈的傳遞
return 'finally的返回值會(huì)被忽略';
})
.then(value => {
console.log('收到:', value); // '成功數(shù)據(jù)',不是'finally的返回值'
})
如果finally拋出錯(cuò)誤,會(huì)覆蓋之前的錯(cuò)誤
Promise.resolve('原始數(shù)據(jù)')
.finally(() => {
throw new Error('finally中的錯(cuò)誤'); // 這個(gè)錯(cuò)誤會(huì)覆蓋之前的結(jié)果
})
.then(value => {
console.log('這不會(huì)執(zhí)行'); // 被跳過(guò)
})
.catch(error => {
console.log('捕獲的錯(cuò)誤:', error.message); // 'finally中的錯(cuò)誤'
});
總結(jié)
方法對(duì)比總結(jié)
| 方法 | 主要用途 | 返回值影響 | 執(zhí)行時(shí)機(jī) | 最佳實(shí)踐 |
|---|---|---|---|---|
| then() | 處理成功結(jié)果,鏈?zhǔn)絺鬟f | 決定下一環(huán)的輸入 | 前一個(gè)Promise完成后 | 保持純函數(shù),明確返回值 |
| catch() | 錯(cuò)誤捕獲和恢復(fù) | 可恢復(fù)錯(cuò)誤或重新拋出 | 鏈中任何錯(cuò)誤發(fā)生時(shí) | 在適當(dāng)層級(jí)處理,不要過(guò)早吞沒(méi)錯(cuò)誤 |
| finally() | 資源清理和狀態(tài)重置 | 不影響結(jié)果傳遞 | 無(wú)論如何都會(huì)執(zhí)行 | 只做清理,不返回業(yè)務(wù)數(shù)據(jù) |
黃金法則
- 單一職責(zé)原則:每個(gè)then()應(yīng)該只做一件事。
- 錯(cuò)誤早拋,晚處理:讓錯(cuò)誤傳播到合適的處理層。
- finally()只清理:不要在finally中返回業(yè)務(wù)邏輯數(shù)據(jù)。
- 保持鏈的可讀性:合理拆分長(zhǎng)鏈,使用命名函數(shù)。
- 考慮可維護(hù)性:為復(fù)雜的鏈添加注釋和文檔。
結(jié)語(yǔ)
本文主要介紹了Promise的三種實(shí)例化方法,對(duì)于文章中錯(cuò)誤的地方或者有任何問(wèn)題!
到此這篇關(guān)于JavaScript Promise實(shí)例方法then()、catch()、finally()的文章就介紹到這了,更多相關(guān)Promise實(shí)例方法then()、catch()、finally()內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js獲取下拉列表框<option>中的value和text的值示例代碼
本篇文章主要是對(duì)js獲取下拉列表框<option>中的value和text的值示例代碼進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01
js實(shí)現(xiàn)網(wǎng)頁(yè)右上角滑出會(huì)自動(dòng)消失大幅廣告的方法
這篇文章主要介紹了js實(shí)現(xiàn)網(wǎng)頁(yè)右上角滑出會(huì)自動(dòng)消失大幅廣告的方法,是javascript廣告特效的典型應(yīng)用,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-02-02
JavaScript中常見(jiàn)的繼承方式總結(jié)
這篇文章主要和大家詳細(xì)介紹了JavaScript中常見(jiàn)的幾種繼承方式,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)JavaScript有一定幫助,需要的小伙伴可以參考下面文章詳細(xì)內(nèi)容2022-11-11
JS如何將數(shù)字金額轉(zhuǎn)換成中文金額格式
這篇文章主要介紹了JS如何將數(shù)字金額轉(zhuǎn)換成中文金額格式,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
JS獲取復(fù)選框的值,并傳遞到后臺(tái)的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇JS獲取復(fù)選框的值,并傳遞到后臺(tái)的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-05-05
JavaScript利用構(gòu)造函數(shù)和原型的方式模擬C#類(lèi)的功能
這篇文章主要介紹了JavaScript利用構(gòu)造函數(shù)和原型的方式模擬C#類(lèi)的功能。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-03-03
javascript的indexOf忽略大小寫(xiě)的方法
javascript 中 indexOf 是嚴(yán)格區(qū)分大小寫(xiě)的 如何才能忽略大小寫(xiě)呢?2008-08-08

