Promise 的出現主要是為了解決 JavaScript 中異步操作的處理問題。在傳統的 JavaScript 中,異步操作通常使用回調函數來處理,但這種方式會導致代碼嵌套層級過深,可讀性差,而且容易產生回調地獄(callback hell)的情況,降低了代碼的可維護性和可理解性。
簡單的説法:它就像是一種特殊的承諾,用來解決 JavaScript 中的異步問題, 想象一下你承諾要跟你朋友五一去北京玩,但是時間還沒到。在等待這個過程完成之前,你可以做其他事情,一旦約定的時間到了,你就要履行承諾,如果你去了就履行,但是你也有權利不履行。
Promise解決什麼問題
回調地獄:如果有多個異步操作依賴於前一個操作的結果,代碼就會出現多層嵌套的回調,造成代碼結構混亂難以維護。
asyncOperation1(callback: () => void) {
setTimeout(function () {
console.log('異步操作1完成');
callback();
}, 1000);
}
asyncOperation2(callback: () => void) {
setTimeout(function () {
console.log('異步操作2完成');
callback();
}, 1500);
}
asyncOperation3(callback: () => void) {
setTimeout(function () {
console.log('異步操作3完成');
callback();
}, 2000);
}
ngOnInit(): void {
console.log('開始');
this.asyncOperation1(() => {
this.asyncOperation2(() => {
this.asyncOperation3(() => {
console.log('所有異步操作完成');
});
});
});
}
開始 異步操作1完成 異步操作2完成 異步操作3完成 所有異步操作完成
錯誤處理困難:錯誤處理通常依賴於回調函數的調用方式,容易出現漏處理或混亂的情況。
asyncOperation(callback: (error: any, result?: number) => void) {
setTimeout( () => {
const randomNumber = Math.random();
if (randomNumber < 0.5) {
callback(null, randomNumber);
} else {
callback(new Error('異步操作失敗'));
}
}, 1000);
}
this.asyncOperation((error: any, result?: number) => {
if (error) {
console.error('異步操作失敗:', error.message);
// 漏處理錯誤,沒有提供錯誤處理邏輯
} else {
console.log('異步操作成功,結果為:', result);
// 只處理了成功情況,沒有考慮錯誤處理的可能性
}
});
小於0.5 異步操作失敗: 異步操作失敗
大於0.5 異步操作成功: 0.52312344 (隨機)
Promise基本使用
Promise 對象通過自身的狀態,來控制異步操作。Promise 實例具有三種狀態。
- 待定(pending):初始狀態,既沒有被兑現,也沒有被拒絕。
- 已兑現(fulfilled):意味着操作成功完成。
- 已拒絕(rejected):意味着操作失敗。
上面三種狀態裏面,fulfilled和rejected合在一起稱為resolved(已定型)。
狀態的變化只有2種
- 從“未完成”到“成功”
- 從“未完成”到“失敗”
狀態一旦發生變化,就不會再發生變化,因此,Promise只有2種結果
- 異步操作成功:從pending到fulfilled
- 異步操作失敗:從pending到rejected
Promise 構造函數
Promise 構造函數是用來創建 Promise 實例的基礎。它接受一個帶有兩個參數的函數作為參數。這個函數會立即執行,通常包含異步操作。這兩個參數一般被稱為 resolve 和 reject,它們是 JavaScript 引擎提供的回調函數。resolve 函數用於表示異步操作成功並返回結果,而 reject 函數用於表示異步操作失敗並返回一個錯誤對象。
const promise = new Promise((resolve, reject) => {
const randomNumber = Math.random();
if (randomNumber < 0.5){
resolve(randomNumber);
} else { /* 異步操作失敗 */
reject(new Error(異步操作失敗));
}
});
Promise 的鏈式調用
.then() 方法最多接受兩個參數;第一個參數是 Promise 兑現時的回調函數,第二個參數是 Promise 拒絕時的回調函數。每個 .then() 返回一個新生成的 Promise 對象,這個對象可被用於鏈式調用,
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("yunzhi");
}, 300);
});
promise
.then((value) => {
console.log(value, '異步操作')
}, (value) => {
console.log(value, '操作失敗')
});
// yunzhi 操作成功
promise
.then((value) => {
console.log(value, '操作成功');
return value + '.club';
}, (value) => {
console.log(value, '操作失敗')
}).then(value => {
console.log(value, '操作成功');
});
// yunzhi 操作成功
// yunzhi.club 操作成功
解決回調地獄
Promise 的鏈式調用可以使代碼更加扁平化、易於理解,並且避免了多層嵌套的回調函數,從而解決了回調地獄問題。
asyncOperation1(callback: () => void) {
setTimeout(function () {
console.log('異步操作1完成');
callback();
}, 1000);
}
asyncOperation2(callback: () => void) {
setTimeout(function () {
console.log('異步操作2完成');
callback();
}, 1500);
}
asyncOperation3(callback: () => void) {
setTimeout(function () {
console.log('異步操作3完成');
callback();
}, 2000);
}
asyncOperation1()
.then(() => {
return asyncOperation2();
})
.then(() => {
return asyncOperation3();
})
.then(() => {
console.log('所有異步操作完成');
});
開始 異步操作1完成 異步操作2完成 異步操作3完成 所有異步操作完成
Promise 的鏈式調用和 .catch() 方法來更可靠地處理錯誤,漏處理或混亂的情況
this.asyncOperation().then((result) => {
console.log('異步操作成功,結果為:', result);
}).catch((error) => {
console.error('異步操作失敗:', error.message);
// 在 .catch() 中處理異步操作失敗的情況
})
asyncOperation() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const randomNumber = Math.random();
if (randomNumber < 0.5) {
resolve(randomNumber);
}
reject(new Error('異步操作1失敗'));
}
, 1000)
})
}
小於0.5 異步操作失敗: 異步操作失敗
大於0.5 異步操作成功: 0.52312344 (隨機)
總結
通過 Promise 構造函數,我們可以更加靈活地處理異步操作,並且避免了傳統回調函數所帶來的回調地獄問題。更好的錯誤處理: Promise 提供了統一的錯誤處理機制,使得錯誤更容易捕獲和處理。通過 .catch() 方法,可以捕獲到 Promise 鏈中的任何一個 Promise 對象的錯誤。