掌握Promise:從基礎到高級應用的全面指
在JavaScript的異步編程領域,Promise無疑是一顆閃耀的明星。它為處理那些麻煩的延遲操作提供了一種簡潔而高效的解決方案。
1 Promise的基本概念
首先,我們得明白Promise是個啥。簡單來說,Promise就是一個代表了尚未完成但預計在未來會完成的操作的容器。它非常類似于一個你會在日后打開的盒子,盒子里可能是你想要的答案,也可能是個壞消息。
創建一個Promise就像制作這樣一個盒子。你用new Promise來制作,里面填上你的異步操作,比如發起一個網絡請求或者讀取一個文件。
Promise有三種狀態:pending(待定)、fulfilled(實現)和rejected(拒絕)。一開始,Promise是pending,表示操作還未完成。一旦操作成功,狀態就會變成fulfilled;如果出了岔子,狀態就會變成rejected。而且,一旦狀態改變,就沒法再變回去了。
2 使用.then()和.catch()方法
在JavaScript中,.then()和.catch()是Promise對象的方法,用于處理異步操作的結果。
- .then()方法:當Promise對象的狀態變為fulfilled(已實現)時,會調用.then()方法中的回調函數,并將異步操作的結果作為參數傳遞給回調函數。.then()方法返回一個新的Promise對象,可以鏈式調用多個.then()方法來處理不同的結果。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功');
}, 1000);
});
promise.then((result) => {
console.log(result); // 輸出 "成功"
}).then((result) => {
console.log('第二個 then');
});
- .catch()方法:當Promise對象的狀態變為rejected(已拒絕)時,會調用.catch()方法中的回調函數,并將錯誤信息作為參數傳遞給回調函數。.catch()方法也可以鏈式調用,用于捕獲前面所有.then()中拋出的錯誤。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('失敗');
}, 1000);
});
promise.then((result) => {
console.log(result);
}).catch((error) => {
console.log(error); // 輸出 "失敗"
});
如果Promise對象的狀態已經是fulfilled或rejected,那么后續的.then()和.catch()將不會執行。另外,如果在.then()中拋出了異常,那么這個異常會被后面的.catch()捕獲。
3 并發執行和Promise的靜態方法
有時候,你需要同時運行多個Promise。這時,Promise.all()和Promise.race()就派上了用場。Promise.all()會等待所有的Promise都完成,然后才繼續。而Promise.race()則不那么耐心,只要有一個Promise完成,不管是fulfilled還是rejected,它就會立即繼續。
- Promise.all(iterable): 這個方法接收一個包含多個Promise對象的可迭代對象(如數組),并返回一個新的Promise對象。這個新的Promise對象會在所有傳入的Promise對象都成功完成時變為fulfilled狀態,并將每個Promise的結果組成一個數組作為參數傳遞給回調函數。如果任何一個Promise失敗,那么新的Promise對象會立即變為rejected狀態,并將第一個失敗的原因作為參數傳遞給回調函數。
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對象中的任意一個完成或失敗時立即改變狀態。無論哪個Promise先完成或失敗,都會將相應的結果或原因傳遞給回調函數。
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()都是異步操作,它們不會阻塞代碼的執行。這意味著你可以在等待這些Promise完成的同時繼續執行其他任務。
4 高級技巧:Promise.prototype.finally()
Promise.prototype.finally()是Promise原型上的一個方法,它用于在Promise鏈的末尾添加一個最終處理程序,無論Promise的狀態是fulfilled還是rejected,這個處理程序都會被調用。這就像是不管你去參加派對后心情如何,回家總是要做的。
4.1 作用
- finally()方法的主要作用是在Promise鏈的最后添加清理或收尾工作,比如關閉文件、釋放資源、清除定時器等。
- 這個方法接受一個回調函數作為參數,這個回調函數沒有參數,也不返回值。
4.2 用法
const promise = new Promise((resolve, reject) => {
// ...異步操作
});
promise
.then(result => {
// ...處理成功結果
})
.catch(error => {
// ...處理錯誤
})
.finally(() => {
// ...執行清理或收尾工作
});
4.3 特點
- 始終執行:無論前面的then()或catch()是否拋出異常,finally()中的回調函數都會被執行。
- 無參數:finally()中的回調函數不接收任何參數,這意味著它不能直接訪問到then()或catch()中的結果或錯誤。
- 返回新的Promise:如果finally()中的回調函數拋出異常,或者返回一個新的Promise,那么這個新的Promise會成為finally()方法的返回值。
4.4 技巧
- 鏈式調用:finally()可以與其他Promise方法進行鏈式調用。
- 錯誤處理:在finally()中可以進行一些通用的錯誤處理,比如記錄日志、發送錯誤報告等。
- 資源清理:finally()非常適合用來執行一些無論成功還是失敗都需要進行的資源清理工作。
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實現,但提供了更加簡潔和直觀的語法。
- async關鍵字:用于聲明一個函數為異步函數。一個被async修飾的函數會自動將返回值包裝成一個Promise對象。如果返回值是thenable對象,那么會等待其解析為最終值;如果返回值是一個原始值,那么會直接解析為該值。
async function asyncFunc() {
return 'hello';
}
const result = asyncFunc(); // result是一個Promise對象
result.then(console.log); // 輸出 "hello"
- await關鍵字:用于在async函數內部等待一個Promise解析為最終值。await暫停代碼執行,直到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的優勢:
- 更簡潔:不需要使用.then()鏈或.catch()來處理結果和錯誤。
- 更好的錯誤處理:可以使用正常的try/catch語句來捕獲異常。
- 順序執行:可以更容易地保證異步操作按預期的順序執行。
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');
我們定義了一個異步函數fetchData,它接受一個URL作為參數。我們使用await來等待fetch()請求的結果,然后再等待將響應體解析為JSON。如果在這個過程中發生任何錯誤,我們可以使用try/catch語句來捕獲并處理它。