掌握Promise:從基礎(chǔ)到高級應(yīng)用的全面指
在JavaScript的異步編程領(lǐng)域,Promise無疑是一顆閃耀的明星。它為處理那些麻煩的延遲操作提供了一種簡潔而高效的解決方案。
1 Promise的基本概念
首先,我們得明白Promise是個啥。簡單來說,Promise就是一個代表了尚未完成但預(yù)計在未來會完成的操作的容器。它非常類似于一個你會在日后打開的盒子,盒子里可能是你想要的答案,也可能是個壞消息。
創(chuàng)建一個Promise就像制作這樣一個盒子。你用new Promise來制作,里面填上你的異步操作,比如發(fā)起一個網(wǎng)絡(luò)請求或者讀取一個文件。
Promise有三種狀態(tài):pending(待定)、fulfilled(實(shí)現(xiàn))和rejected(拒絕)。一開始,Promise是pending,表示操作還未完成。一旦操作成功,狀態(tài)就會變成fulfilled;如果出了岔子,狀態(tài)就會變成rejected。而且,一旦狀態(tài)改變,就沒法再變回去了。
2 使用.then()和.catch()方法
在JavaScript中,.then()和.catch()是Promise對象的方法,用于處理異步操作的結(jié)果。
- .then()方法:當(dāng)Promise對象的狀態(tài)變?yōu)閒ulfilled(已實(shí)現(xiàn))時,會調(diào)用.then()方法中的回調(diào)函數(shù),并將異步操作的結(jié)果作為參數(shù)傳遞給回調(diào)函數(shù)。.then()方法返回一個新的Promise對象,可以鏈?zhǔn)秸{(diào)用多個.then()方法來處理不同的結(jié)果。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功');
}, 1000);
});
promise.then((result) => {
console.log(result); // 輸出 "成功"
}).then((result) => {
console.log('第二個 then');
});
- .catch()方法:當(dāng)Promise對象的狀態(tài)變?yōu)閞ejected(已拒絕)時,會調(diào)用.catch()方法中的回調(diào)函數(shù),并將錯誤信息作為參數(shù)傳遞給回調(diào)函數(shù)。.catch()方法也可以鏈?zhǔn)秸{(diào)用,用于捕獲前面所有.then()中拋出的錯誤。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('失敗');
}, 1000);
});
promise.then((result) => {
console.log(result);
}).catch((error) => {
console.log(error); // 輸出 "失敗"
});
如果Promise對象的狀態(tài)已經(jīng)是fulfilled或rejected,那么后續(xù)的.then()和.catch()將不會執(zhí)行。另外,如果在.then()中拋出了異常,那么這個異常會被后面的.catch()捕獲。
3 并發(fā)執(zhí)行和Promise的靜態(tài)方法
有時候,你需要同時運(yùn)行多個Promise。這時,Promise.all()和Promise.race()就派上了用場。Promise.all()會等待所有的Promise都完成,然后才繼續(xù)。而Promise.race()則不那么耐心,只要有一個Promise完成,不管是fulfilled還是rejected,它就會立即繼續(xù)。
- Promise.all(iterable): 這個方法接收一個包含多個Promise對象的可迭代對象(如數(shù)組),并返回一個新的Promise對象。這個新的Promise對象會在所有傳入的Promise對象都成功完成時變?yōu)閒ulfilled狀態(tài),并將每個Promise的結(jié)果組成一個數(shù)組作為參數(shù)傳遞給回調(diào)函數(shù)。如果任何一個Promise失敗,那么新的Promise對象會立即變?yōu)閞ejected狀態(tài),并將第一個失敗的原因作為參數(shù)傳遞給回調(diào)函數(shù)。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 100, 'foo'));
const promise3 = Promise.resolve(5);
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // 輸出 [3, "foo", 5]
}).catch((error) => {
console.log(error);
});
- Promise.race(iterable): 這個方法也接收一個包含多個Promise對象的可迭代對象,但與Promise.all()不同,它返回一個新的Promise對象,該對象會在傳入的Promise對象中的任意一個完成或失敗時立即改變狀態(tài)。無論哪個Promise先完成或失敗,都會將相應(yīng)的結(jié)果或原因傳遞給回調(diào)函數(shù)。
const promise1 = new Promise((resolve, reject) => setTimeout(resolve, 500, 'one'));
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 100, 'two'));
Promise.race([promise1, promise2]).then((value) => {
console.log(value); // 輸出 "two"
}).catch((error) => {
console.log(error);
});
Promise.all()和Promise.race()都是異步操作,它們不會阻塞代碼的執(zhí)行。這意味著你可以在等待這些Promise完成的同時繼續(xù)執(zhí)行其他任務(wù)。
4 高級技巧:Promise.prototype.finally()
Promise.prototype.finally()是Promise原型上的一個方法,它用于在Promise鏈的末尾添加一個最終處理程序,無論P(yáng)romise的狀態(tài)是fulfilled還是rejected,這個處理程序都會被調(diào)用。這就像是不管你去參加派對后心情如何,回家總是要做的。
4.1 作用
- finally()方法的主要作用是在Promise鏈的最后添加清理或收尾工作,比如關(guān)閉文件、釋放資源、清除定時器等。
- 這個方法接受一個回調(diào)函數(shù)作為參數(shù),這個回調(diào)函數(shù)沒有參數(shù),也不返回值。
4.2 用法
const promise = new Promise((resolve, reject) => {
// ...異步操作
});
promise
.then(result => {
// ...處理成功結(jié)果
})
.catch(error => {
// ...處理錯誤
})
.finally(() => {
// ...執(zhí)行清理或收尾工作
});
4.3 特點(diǎn)
- 始終執(zhí)行:無論前面的then()或catch()是否拋出異常,finally()中的回調(diào)函數(shù)都會被執(zhí)行。
- 無參數(shù):finally()中的回調(diào)函數(shù)不接收任何參數(shù),這意味著它不能直接訪問到then()或catch()中的結(jié)果或錯誤。
- 返回新的Promise:如果finally()中的回調(diào)函數(shù)拋出異常,或者返回一個新的Promise,那么這個新的Promise會成為finally()方法的返回值。
4.4 技巧
- 鏈?zhǔn)秸{(diào)用:finally()可以與其他Promise方法進(jìn)行鏈?zhǔn)秸{(diào)用。
- 錯誤處理:在finally()中可以進(jìn)行一些通用的錯誤處理,比如記錄日志、發(fā)送錯誤報告等。
- 資源清理:finally()非常適合用來執(zhí)行一些無論成功還是失敗都需要進(jìn)行的資源清理工作。
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
console.log(data);
return data;
})
.catch(error => {
console.error('Error:', error);
})
.finally(() => {
console.log('Fetch completed');
});
上述代碼中,無論fetch()請求成功還是失敗,都會在控制臺輸出"Fetch completed"。
5 ES6的async/await
在ES6中,async/await是一種處理異步操作的新方法,它基于Promise實(shí)現(xiàn),但提供了更加簡潔和直觀的語法。
- async關(guān)鍵字:用于聲明一個函數(shù)為異步函數(shù)。一個被async修飾的函數(shù)會自動將返回值包裝成一個Promise對象。如果返回值是thenable對象,那么會等待其解析為最終值;如果返回值是一個原始值,那么會直接解析為該值。
async function asyncFunc() {
return 'hello';
}
const result = asyncFunc(); // result是一個Promise對象
result.then(console.log); // 輸出 "hello"
- await關(guān)鍵字:用于在async函數(shù)內(nèi)部等待一個Promise解析為最終值。await暫停代碼執(zhí)行,直到Promise解析完成,然后返回解析值。如果Promise被拒絕,那么await會拋出異常。
async function asyncFunc() {
const result = await new Promise((resolve, reject) => setTimeout(resolve, 1000, 'world'));
console.log(result); // 1秒后輸出 "world"
}
asyncFunc();
使用async/await的優(yōu)勢:
- 更簡潔:不需要使用.then()鏈或.catch()來處理結(jié)果和錯誤。
- 更好的錯誤處理:可以使用正常的try/catch語句來捕獲異常。
- 順序執(zhí)行:可以更容易地保證異步操作按預(yù)期的順序執(zhí)行。
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('There has been a problem with your fetch operation:', error);
}
}
fetchData('https://api.example.com/data');
我們定義了一個異步函數(shù)fetchData,它接受一個URL作為參數(shù)。我們使用await來等待fetch()請求的結(jié)果,然后再等待將響應(yīng)體解析為JSON。如果在這個過程中發(fā)生任何錯誤,我們可以使用try/catch語句來捕獲并處理它。