九個Promises的必知高級用法
概述
Promise 對象表示異步操作的最終完成(或失敗)及其結果值。
Promise 始終處于以下狀態之一:
- 待處理:初始狀態,既未實現也未拒絕。
- 已實現:操作已成功完成。
- 已拒絕:操作失敗。
與“舊式”回調不同,使用 Promises 具有以下約定:
- 在當前事件循環完成之前,不會調用回調函數。
- 即使異步操作完成(成功或失敗),之后通過 then() 添加的回調仍將被調用。
- 可以通過多次調用 then() 來添加多個回調,它們將按照添加的順序執行。
Promises 的典型特征是鏈接。
一般用法
1.Promise.all([])
當數組中的所有 Promise 實例都成功時,它會按請求的順序返回成功結果數組。如果任何 Promise 失敗,它將進入失敗回調。
const p1 = new Promise((resolve) => {
resolve(1);
});
const p2 = new Promise((resolve) => {
resolve(1);
});
const p3 = Promise.resolve('ok');
// If all promises succeed, result will be an array of 3 results.
const result = Promise.all([p1, p2, p3]);
// If one fails, the result is the failed promise's value.
2. Promise.allSettled([])
執行不會失敗;它返回與輸入數組中每個 Promise 實例的狀態相對應的數組。
const p1 = Promise.resolve(1);
const p2 = Promise.reject(-1);
Promise.allSettled([p1, p2]).then(res => {
console.log(res);
});
// Output:
/*
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: -1 }
]
*/
3. Promise.any([])
如果輸入數組中的任何 Promise 滿足條件,則返回的實例將變為滿足條件并返回第一個滿足條件的 Promise 的值。如果所有 Promise 均被拒絕,則將變為拒絕條件。
const p1 = new Promise((resolve, reject) => {
reject(1);
});
const p2 = new Promise((resolve, reject) => {
reject(2);
});
const p3 = Promise.resolve("ok");
Promise.any([p1, p2, p3]).then(
(r) => console.log(r), // Outputs 'ok'
(e) => console.log(e)
);
4. Promise.race([])
只要數組中的任何 Promise 改變狀態,race 方法的狀態就會隨之改變;第一個改變的 Promise 的值將傳遞給 race 方法的回調。
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve(10);
}, 3000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
throw new Error("I encountered an error");
}, 2000);
});
Promise.race([p1, p2]).then(
(v) => console.log(v), // Outputs 10
(e) => console.log(e)
);
拋出異常不會改變競爭狀態;它仍然由 p1 決定。
高級用法
以下是 9 種高級用法,可幫助開發人員更高效、更優雅地處理異步操作。
1.并發控制
使用 Promise.all 允許并行執行多個 Promises,但要控制同時請求的數量,您可以實現并發控制功能。
const concurrentPromises = (promises, limit) => {
return new Promise((resolve, reject) => {
let i = 0;
let result = [];
const executor = () => {
if (i >= promises.length) {
return resolve(result);
}
const promise = promises[i++];
Promise.resolve(promise)
.then(value => {
result.push(value);
if (i < promises.length) {
executor();
} else {
resolve(result);
}
})
.catch(reject);
};
for (let j = 0; j < limit && j < promises.length; j++) {
executor();
}
});
};
2. Promise 超時
有時,可能希望 Promise 在特定時間范圍內未解析時自動拒絕。這可以按如下方式實現。
const promiseWithTimeout = (promise, ms) =>
Promise.race([
promise,
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error('Timeout after ' + ms + 'ms')), ms)
)
]);
3. 取消 Promises
原生 JavaScript Promises 無法取消,但你可以通過引入可控中斷邏輯來模擬取消。
const cancellablePromise = promise => {
let isCanceled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
value => (isCanceled ? reject({ isCanceled, value }) : resolve(value)),
error => (isCanceled ? reject({ isCanceled, error }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
isCanceled = true;
}
};
};
4. Promise 數組的順序執行
有時您需要按順序執行一系列 Promise,確保前一個異步操作完成后再開始下一個操作。
const sequencePromises = promises =>
promises.reduce(
(prev, next) => prev.then(() => next()),
Promise.resolve()
);
5. Promise 的重試邏輯
當 Promise 因臨時錯誤而被拒絕時,您可能希望重試其執行。
const retryPromise = (promiseFn, maxAttempts, interval) => {
return new Promise((resolve, reject) => {
const attempt = attemptNumber => {
if (attemptNumber === maxAttempts) {
reject(new Error('Max attempts reached'));
return;
}
promiseFn().then(resolve).catch(() => {
setTimeout(() => {
attempt(attemptNumber + 1);
}, interval);
});
};
attempt(0);
});
};
6. 確保 Promise 僅解析一次
在某些情況下,您可能希望確保 Promise 僅解析一次,即使多次調用 resolve。
const onceResolvedPromise = executor => {
let isResolved = false;
return new Promise((resolve, reject) => {
executor(
value => {
if (!isResolved) {
isResolved = true;
resolve(value);
}
},
reject
);
});
};
7. 使用 Promises 代替回調
Promises 通過替代回調函數,提供了一種更標準化、更方便的方式來處理異步操作。
const callbackToPromise = (fn, ...args) => {
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
8. 動態生成 Promise 鏈
在某些情況下,您可能需要根據不同的條件動態創建一系列 Promise 鏈。
const tasks = [task1, task2, task3]; // Array of asynchronous tasks
const promiseChain = tasks.reduce((chain, currentTask) => {
return chain.then(currentTask);
}, Promise.resolve());
9. 使用 Promises 實現簡單的異步鎖
在多線程環境中,可以使用 Promises實現簡單的異步鎖,確保每次只有一個任務可以訪問共享資源。
let lock = Promise.resolve();
const acquireLock = () => {
let release;
const waitLock = new Promise(resolve => {
release = resolve;
});
const tryAcquireLock = lock.then(() => release);
lock = waitLock;
return tryAcquireLock;
};
這段代碼不斷創建和解析 Promise,實現了一個簡單的 FIFO 隊列,確保只有一個任務可以訪問共享資源。
lock 變量表示當前是否有任務正在執行,始終指向正在進行的任務的 Promise。
acquireLock 函數請求執行權限并創建一個新的 Promise 以等待當前任務完成。
結論
Promise 是現代 JavaScript 異步編程不可或缺的一部分。
掌握它們的高級技巧將大大提高開發效率和代碼質量。通過上述各種方法,開發人員可以更自信地處理復雜的異步場景,編寫更易讀、更優雅、更健壯的代碼。