标准答案
- promisify 是把 Node 风格回调函数转换成 Promise 函数。核心是返回一个新函数,内部调用原函数,并追加回调 (err, data)。err 存在就 reject,否则 resolve(data)。实现时要保留 this 和参数。
- sleep 是一个延迟 Promise,常见写法是 new Promise(resolve => setTimeout(resolve, ms)),适合测试、轮询间隔、重试等待。
- timeout 通常用 Promise.race 把业务 Promise 和超时 Promise 竞争,超时后 reject。它只让外层等待失败,不一定取消底层任务。
- retry 要接收任务函数,而不是已经执行的 Promise。失败时重新调用任务函数,直到成功或达到最大次数,可以加入固定延迟、指数退避和 AbortSignal。
题目解析
这类题的重点不是背 API,而是处理异步边界:错误如何传递、任务什么时候开始、是否能取消、重试是否真的重新执行。
timeout 如果不和 AbortController 配合,底层 fetch 或异步任务可能仍在继续运行。调用方只是不再等待它。
retry 传入同一个 Promise 没有意义,因为 Promise 一旦创建通常已经开始执行。要真正重试,必须传入能重新发起任务的函数。
代码示例
下面示例包含 promisify、sleep、timeout 和带延迟的 retry。
JavaScript
function promisify(fn) {
return function promisified(...args) {
return new Promise((resolve, reject) => {
fn.call(this, ...args, (error, data) => {
if (error) {
reject(error)
return
}
resolve(data)
})
})
}
}
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))
function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) => {
setTimeout(() => reject(new Error('timeout')), ms)
})
])
}
async function retry(task, maxTimes = 3, delay = 300) {
let lastError
for (let current = 1; current <= maxTimes; current += 1) {
try {
return await task()
} catch (error) {
lastError = error
if (current < maxTimes) {
await sleep(delay)
}
}
}
throw lastError
}常见误区
- promisify 时丢失原函数 this。
- timeout 只 reject 外层 Promise,但没有取消底层请求。
- retry 没有限制次数,失败后无限循环。