JavaScript?Promise錯誤處理與最佳實踐
一句話概括:Promise 是 ES6 引入的異步編程解決方案,用于表示一個異步操作的最終完成或失敗,以及其返回的值。它解決了“回調(diào)地獄”(Callback Hell)問題,使異步代碼更清晰、可讀、可維護。
一、為什么需要Promise?(歷史背景)
在 Promise 出現(xiàn)之前,JavaScript 主要通過 回調(diào)函數(shù)(Callback) 處理異步操作:
// 回調(diào)地獄(Callback Hell)
doTask1((err, result1) => {
if (err) return handleError(err);
doTask2(result1, (err, result2) => {
if (err) return handleError(err);
doTask3(result2, (err, result3) => {
if (err) return handleError(err);
console.log('完成:', result3);
});
});
});
回調(diào)函數(shù)的痛點:
- 嵌套層級深,難以閱讀
- 錯誤處理重復
- 無法
return或throw - 控制流復雜(并行、串行、競態(tài)等)
Promise 的出現(xiàn)就是為了解決這些問題。
二、Promise的三種狀態(tài)(States)
Promise 對象有且僅有三種狀態(tài),一旦改變不可逆:
| 狀態(tài) | 說明 |
|---|---|
pending | 初始狀態(tài),進行中 |
fulfilled | 成功狀態(tài),操作成功完成 |
rejected | 失敗狀態(tài),操作失敗 |
const promise = new Promise((resolve, reject) => {
// 初始狀態(tài):pending
if (success) {
resolve(value); // → fulfilled
} else {
reject(error); // → rejected
}
});
三、Promise構造函數(shù)
new Promise(executor)
executor是一個函數(shù),格式:(resolve, reject) => {}resolve(value):將 Promise 狀態(tài)變?yōu)?fulfilledreject(error):將 Promise 狀態(tài)變?yōu)?rejectedexecutor會立即執(zhí)行
const p = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve('成功') : reject('失敗');
}, 1000);
});
resolve不同值的區(qū)別
- 普通值或者對象,那么這個值會作為
then回調(diào)的參數(shù) - 另外一個
Promise對象,那么這個新Promise會決定原來Promise的狀態(tài) thenable對象:這對象中有實現(xiàn)then方法,那么就會執(zhí)行該then方法,并且根據(jù)then方法的結果來決定Promise的狀態(tài)
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 3000);
});
const promise = new Promise((resolve, reject) => {
// 1. 普通值
// resolve(1);
// resolve([
// {
// name: "風茫",
// age: 18,
// },
// {
// name: "fengmang",
// age: 30,
// },
// ]);
// 2. resolve(promise)
// 如果resolve的值為Promise對象,那么當前的Promise的狀態(tài)由傳入的promise來決定
// resolve(p);
// 3. resolve(thenable對象)
// thenable對象: 對象有then方法,那么就會執(zhí)行then方法,并且根據(jù)then方法的結果來決定Promise的狀態(tài)
resolve({
name: "風茫",
then: function (resolve, reject) {
Math.random() > 0.5 ? resolve(100) : reject("失敗");
},
});
});
promise
.then((res) => {
console.log("res :>> ", res);
})
.catch((err) => {
console.log(err);
});四、Promise的核心方法
1..then(onFulfilled, onRejected)
注冊成功和失敗的回調(diào)。
p.then(
(value) => { console.log(value); }, // fulfilled
(error) => { console.error(error); } // rejected
);鏈式調(diào)用的關鍵:
.then()返回一個新的Promise
then方法返回值
const promise = new Promise((resolve, reject) => {
resolve("aaa");
});
const p2 = promise
.then((res) => {
console.log(res);
return "bbb";
})
.then((res) => {
console.log(res);
return "ccc";
});
p2.then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
});
/**
* 輸出結果:
* aaa
* bbb
* ccc
*/解釋
- Promise的
then方法是返回一個新的Promise,這個新Promise的決議是等到then方法傳入的回調(diào)函數(shù) 有返回值 時進行決議 - 若是上一個
then方法沒有使用return返回,則下一個then方法接收到的值是undefined - 若是在
then方法中return一個新的Promise,則下一個then方法需要等到新Promise決議之后才會執(zhí)行,將新的Promise決議結果返回給下一個then方法并執(zhí)行then方法
2..catch(onRejected)
捕獲錯誤,相當于 .then(null, onRejected)
p.catch(err => {
console.error('出錯了:', err);
});
推薦寫法:鏈式 .then().catch(),統(tǒng)一錯誤處理
fetch('/api/data')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error('請求失敗:', err));
3..finally(onFinally)
無論成功或失敗都會執(zhí)行,常用于清理資源。
p.finally(() => {
console.log('請求完成,關閉 loading...');
});
不接收參數(shù),不改變結果,常用于加載狀態(tài)、資源釋放。
五、Promise靜態(tài)方法
1.Promise.resolve(value)
作用:返回一個 fulfilled 的 Promise。
Promise.resolve(42).then(x => console.log(x)); // 42
等價于:
new Promise(resolve => resolve(42));
使用場景:
- 將值包裝為Promise:如果你有一個立即可用的值,但是你希望以 Promise 的形式來處理它(比如為了保持接口的一致性),你可以使用
Promise.resolve()來將這個值包裝成一個已解決的 Promise。
let promise = Promise.resolve(42);
promise.then(function(value) {
console.log(value); // 輸出: 42
});
- 處理未知的返回值:當你不確定某個函數(shù)會返回一個普通值還是一個 Promise,但你希望統(tǒng)一用 then 方法來處理時,可以使用
Promise.resolve()來包裹該返回值。
function maybeAsync() {
if (/* some condition */) {
return new Promise(/* ... */);
} else {
return 'immediate value';
}
}
Promise.resolve(maybeAsync()).then(function(result) {
console.log(result);
});- 模擬異步行為:有時候,即使數(shù)據(jù)已經(jīng)準備好了,你也可能想要通過
Promise.resolve()來“延遲”執(zhí)行某些代碼,以便于和其他異步操作保持一致的流程控制。
Promise.resolve().then(() => {
console.log('This runs after the current call stack is cleared.');
});
- Promise 鏈條的開始:在構建一個由多個步驟組成的 Promise 鏈時,如果沒有初始的 Promise 可供鏈式調(diào)用
.then()或.catch()方法,可以使用Promise.resolve()作為起點。
Promise.resolve()
.then(() => console.log('First step'))
.then(() => console.log('Second step'));
2.Promise.reject(reason)
返回一個 rejected 的 Promise。
Promise.reject('出錯了').catch(err => console.log(err));
3.Promise.all(iterable)
作用:接收一個可迭代對象(通常是數(shù)組),其中每個元素都是一個 Promise,并發(fā)執(zhí)行多個 Promise,返回一個新的Promise。全部成功才成功,任一失敗則整體失敗。
const p1 = Promise.resolve('A');
const p2 = Promise.resolve('B');
const p3 = Promise.reject('C');
Promise.all([p1, p2]).then(results => {
console.log(results); // ['A', 'B']
});
Promise.all([p1, p2, p3]).catch(err => {
console.log(err); // 'C'
});適用于“所有請求都必須成功”的場景(如批量上傳)。
4.Promise.race(iterable)
返回第一個完成的 Promise(無論是成功還是失?。?/p>
const slow = new Promise(r => setTimeout(() => r('慢'), 2000));
const fast = new Promise(r => setTimeout(() => r('快'), 500));
Promise.race([slow, fast]).then(result => {
console.log(result); // '快'
});適用于“超時控制”:
function timeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('超時')), ms)
);
return Promise.race([promise, timeout]);
}5.Promise.allSettled(iterable)
等待所有 Promise 結束,無論成功或失敗,返回結果數(shù)組。
Promise.allSettled([p1, p2, p3]).then(results => {
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`p${i+1} 成功:`, result.value);
} else {
console.log(`p${i+1} 失敗:`, result.reason);
}
});
});
適用于“不關心成敗,只想知道所有結果”的場景。
6.Promise.any(iterable)
返回第一個成功的 Promise;如果全部失敗,則拋出 AggregateError。
const fail1 = Promise.reject('錯誤1');
const fail2 = Promise.reject('錯誤2');
const success = Promise.resolve('成功');
Promise.any([fail1, fail2, success]).then(res => {
console.log(res); // '成功'
});適用于“只要有一個成功即可”的場景(如多源請求)。
六、Promise的執(zhí)行機制(微任務)
Promise 的回調(diào)(.then、.catch)屬于 微任務(microtask),在當前宏任務結束后立即執(zhí)行。
console.log(1);
Promise.resolve().then(() => {
console.log(2);
});
console.log(3);
// 輸出:1 → 3 → 2微任務優(yōu)先級高于
setTimeout(宏任務)
setTimeout(() => console.log(1), 0); Promise.resolve().then(() => console.log(2)); console.log(3); // 輸出:3 → 2 → 1
七、錯誤處理最佳實踐
正確方式:使用.catch()
fetch('/api')
.then(res => res.json())
.then(data => { throw new Error('處理失敗'); })
.catch(err => console.error(err)); // 能捕獲
錯誤方式:在.then中不處理錯誤
fetch('/api')
.then(res => res.json(), err => console.error(err)) // 只能捕獲 fetch 失敗
.then(data => { throw new Error('處理失敗'); }) // 這個錯誤沒被捕獲!
建議:鏈式調(diào)用末尾加
.catch()
八、Promise的常見模式
1. 串行執(zhí)行(鏈式調(diào)用)
function asyncTask(name) {
return Promise.resolve().then(() => {
console.log(`執(zhí)行 ${name}`);
return name;
});
}
asyncTask('A')
.then(() => asyncTask('B'))
.then(() => asyncTask('C'));2. 并行執(zhí)行
Promise.all([
asyncTask('A'),
asyncTask('B'),
asyncTask('C')
]).then(results => console.log('全部完成'));
3. 重試機制
function retry(fn, times = 3) {
return fn().catch(err => {
if (times <= 1) throw err;
return retry(fn, times - 1);
});
}
retry(() => fetch('/api')).catch(console.error);九、Promise的局限性
| 問題 | 說明 |
|---|---|
| 無法取消 | 一旦創(chuàng)建,無法中途取消 |
| 錯誤冒泡 | 未捕獲的錯誤可能靜默失?。∟ode.js 會警告) |
| 無法獲取進度 | 只有成功/失敗,不支持 onProgress |
冗長的 .then 鏈 | 復雜邏輯仍難讀 |
解決方案:使用
async/await(ES2017)
async function getData() {
try {
const res = await fetch('/api');
const data = await res.json();
return data;
} catch (err) {
console.error('請求失敗:', err);
}
}
十、總結
| 項目 | 說明 |
|---|---|
| 定位 | 異步編程的標準化解決方案 |
| 核心價值 | 解決回調(diào)地獄,提供鏈式調(diào)用和統(tǒng)一錯誤處理 |
| 關鍵方法 | .then()、.catch()、.finally() |
| 靜態(tài)方法 | all、race、allSettled、any、resolve、reject |
| 執(zhí)行機制 | 回調(diào)屬于微任務,優(yōu)先級高 |
| 現(xiàn)代替代 | async/await(基于 Promise) |
最終結論:Promise 是現(xiàn)代 JavaScript 異步編程的基石。即使你使用 async/await,其底層仍是 Promise。掌握 Promise 是成為合格前端/Node.js 開發(fā)者的必經(jīng)之路。
到此這篇關于JavaScript Promise錯誤處理與最佳實踐的文章就介紹到這了,更多相關js promise內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
動態(tài)創(chuàng)建樣式表在各瀏覽器中的差異測試代碼
對于標準瀏覽器,直接使用css.innerHTML也可以修改HTMLStyleElement的css規(guī)則2011-09-09
uniapp實現(xiàn)下拉刷新與上拉觸底加載功能的示例代碼
這篇文章主要記錄一下uniapp實現(xiàn)下拉刷新與上拉觸底加載功能的示例代碼,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2023-04-04
使用typescript+webpack構建一個js庫的示例詳解
這篇文章主要介紹了typescript+webpack構建一個js庫,本文主要記錄使用typescript配合webpack打包一個javascript library的配置過程,需要的朋友可以參考下2022-07-07
BootStrap 智能表單實戰(zhàn)系列(二)BootStrap支持的類型簡介
這篇文章主要介紹了BootStrap 智能表單實戰(zhàn)系列(二)BootStrap支持的類型簡介 的相關資料,非常不錯具有參考借鑒價值,感興趣的朋友一起學習吧2016-06-06
javascript設計模式 – 狀態(tài)模式原理與用法實例分析
這篇文章主要介紹了javascript設計模式 – 狀態(tài)模式,結合實例形式分析了javascript狀態(tài)模式相關概念、原理、用法及操作注意事項,需要的朋友可以參考下2020-04-04

