本文深入解析 JavaScript 中 Promise 的 23 种高级用法,涵盖并发控制、超时处理、取消机制、状态检测及重试逻辑等核心场景。通过具体代码示例,展示如何利用 Promise.all、Promise.race 及 async/await 优化异步流程,提升代码健壮性与执行效率。
在 JavaScript 开发中,Promise 是处理异步逻辑的核心机制。它代表了一个异步操作最终完成(或失败)及其结果值的抽象。本文梳理了 23 种高级应用场景,旨在帮助开发者更高效、优雅地驾驭异步编程,提升代码的可维护性与执行效率。
1. 并发控制
利用 Promise.all 可以并行执行多个 Promise。然而,当需要限制同时发起的请求数量以保护服务器或客户端资源时,可以通过封装一个并发控制函数来限制同时运行的 Promise 数量。
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 在指定时间内未返回结果,我们希望自动将其拒绝(reject)。这可以通过包装一个定时器 Promise 来实现超时逻辑。
const promiseWithTimeout = (promise, ms) =>
Promise.race([
promise,
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error('Timeout after ' + ms + 'ms')), ms)
)
]);
3. Promise 的取消
原生 Promise 不支持直接取消操作。但我们可以通过引入一个内部状态标志来模拟取消机制,从而在需要时中断 Promise 的执行流程。
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 不暴露内部状态供直接查询。不过,可以通过封装技巧来探测当前 Promise 是处于已解决(fulfilled)、已拒绝(rejected)还是待定(pending)状态。
const reflectPromise = promise =>
promise.then(
value => ({ status: 'fulfilled', value }),
error => ({ status: 'rejected', error })
);
5. 顺序执行 Promise 数组
当需要确保前一个异步操作完全结束后再启动下一个时,应按顺序执行一组 Promise。这通常通过链式调用或 reduce 方法实现串行化执行。
const sequencePromises = promises =>
promises.reduce(
(prev, next) => prev.then(() => next()),
Promise.resolve()
);
6. 基于条件的 Promise 链
在某些业务逻辑中,是否需要执行后续 Promise 取决于前置条件的判断结果。可以根据条件动态决定是否继续执行下一个异步操作。
const conditionalPromise = (conditionFn, promise) =>
conditionFn() ? promise : Promise.resolve();
7. 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);
});
};
8. 确保 Promise 只解决一次
在某些复杂场景下,即使 resolve 被多次调用,也需确保 Promise 仅被解决一次。这可以通过内部状态标记来防止重复解决。
const onceResolvedPromise = executor => {
let isResolved = false;
return new Promise((resolve, reject) => {
executor(
value => {
if (!isResolved) {
isResolved = true;
resolve(value);
}
},
reject
);
});
};
9. 使用 Promise.allSettled 处理多个异步操作
与 Promise.all 不同,Promise.allSettled 会等待所有 Promise 结束,无论每个 Promise 是成功(fulfilled)还是失败(rejected)。它返回一个包含每个结果状态的对象数组。
const promises = [fetch('/api/endpoint1'), fetch('/api/endpoint2')];
Promise.allSettled(promises).then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index + 1} succeeded with value ${result.value}`);
} else {
console.error(`Promise ${index + 1} failed with reason ${result.reason}`);
}
});
});
10. 处理多个 Promises 的最快响应
当处理多个异步请求且只关心最先返回的结果时,可以使用 Promise.race 来实现。它会立即返回第一个完成的 Promise 的结果,忽略其他未完成的请求。
const promises = [fetch('/api/endpointA'), fetch('/api/endpointB')];
Promise.race(promises)
.then(value => {
// 处理最快的响应
})
.catch(reason => {
// 处理最早的拒绝
});
11. 使用 async/await 简化 Promise
async 和 await 关键字允许以同步代码的写法编写异步逻辑,使代码结构更清晰,逻辑更易于理解和维护。
async function asyncFunction() {
try {
const result = await aPromise;
// Do something with result
} catch (error) {
// Handle error
}
}
12. 连续获取不确定数量的数据页
在获取分页数据且总页数未知的情况下,可以通过递归方式不断请求下一页,直到没有更多数据为止,从而获取完整数据集。
async function fetchPages(apiEndpoint, page = 1, allResults = []) {
const response = await fetch(`${apiEndpoint}?page=${page}`);
const data = await response.json();
if (data.nextPage) {
return fetchPages(apiEndpoint, page + 1, allResults.concat(data.results));
} else {
return allResults.concat(data.results);
}
}
13. 映射并发 Promises 并处理结果数组
当需要并发执行一组异步函数并收集所有结果时,可以使用 Promise.all。它将多个 Promise 并行执行,并在全部完成后返回结果数组。
const fetchUrls = urls => {
const fetchPromises = urls.map(url => fetch(url).then(response => response.json()));
return Promise.all(fetchPromises);
};
14. 使用 Generators 管理流程
通过将 async/await 与 Generators(生成器)结合,可以构建一个可控制的异步流程管理器,实现更复杂的异步逻辑编排。
function* asyncGenerator() {
const result1 = yield aPromise1;
const result2 = yield aPromise2(result1);
// ...
}
15. 使用 Promises 替代回调
Promise 提供了一种更标准、更便捷的异步处理范式,通过将传统的回调函数替换为 Promise 链,可以显著减少“回调地狱”问题。
const callbackToPromise = (fn, ...args) => {
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
16. 流式处理大型数据集
处理大型数据集时,建议使用流式方式获取和处理数据,以避免一次性加载导致内存溢出。Promise 可以结合流式处理技术来优化内存使用。
async function processLargeDataSet(dataSet) {
for (const dataChunk of dataSet) {
const processedChunk = await process(dataChunk); // Returns a Promise
await save(processedChunk); // Another async operation
}
}
17. 同时执行多个异步任务并处理中途的失败
有时即使其中一个异步任务失败,也希望其他任务能继续执行并完成。这可以通过捕获单个 Promise 的错误并继续处理其他 Promise 来实现。
const promises = [promise1, promise2, promise3];
Promise.all(promises.map(reflectPromise)).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
// Do something with result.value
} else {
// Handle result.error
}
});
});
18. Promise-pipeline
通过管道化(pipeline)方式,可以依次执行一系列异步操作,将前一个操作的输出作为下一个操作的输入,实现数据的逐步转换。
const promisePipe = (...fns) => value => fns.reduce((p, f) => p.then(f), Promise.resolve(value));
19. 使用 promise 实现一个延时
可以结合 setTimeout 和 Promise 来实现一个异步延时函数,常用于模拟异步操作或控制执行节奏。
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
20. 动态生成 Promise 链
在某些情况下,可能需要根据运行时条件动态生成一系列 Promise 链。这可以通过条件判断和递归或循环构建 Promise 序列来实现。
const tasks = [task1, task2, task3]; // Array of asynchronous tasks
const promiseChain = tasks.reduce((chain, currentTask) => {
return chain.then(currentTask);
}, Promise.resolve());
21. 使用 Promise 实现简易的异步锁
在单线程的 JavaScript 环境中,可以使用 Promise 来实现简易的异步锁,防止多个异步操作同时访问共享资源,确保数据一致性。
let lock = Promise.resolve();
const acquireLock = () => {
let release;
const waitLock = new Promise(resolve => {
release = resolve;
});
const tryAcquireLock = lock.then(() => release);
lock = waitLock;
return tryAcquireLock;
};
22. 组合多个 Promise 操作为一个函数
可以将多个常用的 Promise 操作封装为一个高阶函数,通过函数复用减少冗余代码,提高代码的可读性和可维护性。
const fetchDataAndProcess = async url => {
const data = await fetch(url).then(resp => resp.json());
return processData(data);
};
23. 处理可选的异步操作
在某些场景下,某个异步操作是可选的。可以通过条件判断决定是否执行该 Promise,并在必要时提供默认值或空处理。
async function optionallyAsyncTask(condition, asyncOperation, fallbackValue) {
if (condition) {
return await asyncOperation;
} else {
return fallbackValue;
}
}
结语
Promise 是现代 JavaScript 异步编程的基石,掌握其高级技巧能显著提升开发效率和代码质量。通过上述多种用法,开发者可以更自信地应对复杂的异步场景,编写出更可读、优雅且健壮的代码。
以上就是 JavaScript 中 Promise 高级用法的详细内容,更多关于 JavaScript Promise 的资料请关注本站其它相关文章!