sleep 函数
问题
实现一个 sleep 函数,使程序暂停执行指定的时间。
答案
sleep 函数是异步编程的基础工具,通过 Promise 和 setTimeout 实现。
基础实现
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// 使用
async function main() {
console.log('开始');
await sleep(2000);
console.log('2秒后');
}
main();
带返回值的 sleep
function sleepWithValue<T>(ms: number, value: T): Promise<T> {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
// 使用
const result = await sleepWithValue(1000, 'Hello');
console.log(result); // 'Hello'
可取消的 sleep
interface CancelableSleep {
promise: Promise<void>;
cancel: () => void;
}
function cancelableSleep(ms: number): CancelableSleep {
let timerId: ReturnType<typeof setTimeout>;
let rejectFn: (reason?: unknown) => void;
const promise = new Promise<void>((resolve, reject) => {
rejectFn = reject;
timerId = setTimeout(resolve, ms);
});
const cancel = () => {
clearTimeout(timerId);
rejectFn(new Error('Sleep cancelled'));
};
return { promise, cancel };
}
// 使用
const { promise, cancel } = cancelableSleep(5000);
// 2 秒后取消
setTimeout(cancel, 2000);
try {
await promise;
console.log('完成');
} catch (e) {
console.log('被取消');
}
AbortController 版本
function sleepWithAbort(ms: number, signal?: AbortSignal): Promise<void> {
return new Promise((resolve, reject) => {
if (signal?.aborted) {
reject(new Error('已取消'));
return;
}
const timerId = setTimeout(() => {
resolve();
}, ms);
signal?.addEventListener('abort', () => {
clearTimeout(timerId);
reject(new Error('Sleep aborted'));
});
});
}
// 使用
const controller = new AbortController();
// 1 秒后取消
setTimeout(() => controller.abort(), 1000);
try {
await sleepWithAbort(5000, controller.signal);
} catch (e) {
console.log('被取消');
}
带超时的 sleep
function sleepWithTimeout(
ms: number,
timeout: number
): Promise<'completed' | 'timeout'> {
return Promise.race([
sleep(ms).then(() => 'completed' as const),
sleep(timeout).then(() => 'timeout' as const),
]);
}
// 使用
const result = await sleepWithTimeout(3000, 2000);
if (result === 'timeout') {
console.log('超时了');
}
delay 工具函数
// 延迟执行函数
function delay<T>(ms: number, fn: () => T): Promise<T> {
return new Promise((resolve) => {
setTimeout(() => resolve(fn()), ms);
});
}
// 使用
const result = await delay(1000, () => {
console.log('1 秒后执行');
return 'result';
});
随机延迟
function randomSleep(min: number, max: number): Promise<void> {
const ms = Math.floor(Math.random() * (max - min + 1)) + min;
return sleep(ms);
}
// 使用:随机等待 1-3 秒
await randomSleep(1000, 3000);
轮询等待
async function waitUntil(
condition: () => boolean | Promise<boolean>,
interval = 100,
timeout = 10000
): Promise<boolean> {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
if (await condition()) {
return true;
}
await sleep(interval);
}
return false;
}
// 使用:等待元素出现
const found = await waitUntil(
() => document.querySelector('#target') !== null,
100,
5000
);
测试用 mock
// 用于测试的 mock sleep
let mockEnabled = false;
function mockableSleep(ms: number): Promise<void> {
if (mockEnabled) {
return Promise.resolve();
}
return sleep(ms);
}
// 测试中启用 mock
function enableMock() {
mockEnabled = true;
}
function disableMock() {
mockEnabled = false;
}
高精度 sleep(requestAnimationFrame)
function precisionSleep(ms: number): Promise<void> {
return new Promise((resolve) => {
const start = performance.now();
function check() {
if (performance.now() - start >= ms) {
resolve();
} else {
requestAnimationFrame(check);
}
}
requestAnimationFrame(check);
});
}
// 注意:精度更高但会消耗更多 CPU
批量延迟执行
async function sleepBetween<T>(
items: T[],
ms: number,
fn: (item: T, index: number) => Promise<void> | void
): Promise<void> {
for (let i = 0; i < items.length; i++) {
await fn(items[i], i);
if (i < items.length - 1) {
await sleep(ms);
}
}
}
// 使用:每个请求间隔 500ms
await sleepBetween(['a', 'b', 'c'], 500, async (item) => {
console.log(item);
await fetch(`/api/${item}`);
});
Node.js 原生方案
// Node.js 16+ 内置 timers/promises
import { setTimeout as sleep } from 'timers/promises';
await sleep(1000);
console.log('1 秒后');
// 带取消
const controller = new AbortController();
try {
await sleep(5000, undefined, { signal: controller.signal });
} catch (e) {
if (e.code === 'ABORT_ERR') {
console.log('已取消');
}
}
常见面试问题
Q1: setTimeout 的最小延迟是多少?
答案:
| 环境 | 最小延迟 |
|---|---|
| 浏览器(嵌套深度 >4) | 4ms |
| 浏览器(其他情况) | 0-1ms |
| Node.js | 1ms |
// 即使设置 0,也有最小延迟
setTimeout(() => console.log('执行'), 0);
console.log('同步代码');
// 输出:同步代码 -> 执行
Q2: sleep 和 setTimeout 的区别?
答案:
// setTimeout:回调风格
setTimeout(() => {
console.log('1 秒后');
setTimeout(() => {
console.log('2 秒后');
}, 1000);
}, 1000);
// sleep:可以使用 await
await sleep(1000);
console.log('1 秒后');
await sleep(1000);
console.log('2 秒后');
sleep 返回 Promise,可以配合 async/await 使用,避免回调地狱。
Q3: 如何实现精确的定时?
答案:
setTimeout 不保证精确时间,可能因事件循环延迟。补偿方案:
function accurateInterval(fn: () => void, interval: number): () => void {
let expected = Date.now() + interval;
let timerId: ReturnType<typeof setTimeout>;
function step() {
const drift = Date.now() - expected;
fn();
expected += interval;
timerId = setTimeout(step, Math.max(0, interval - drift));
}
timerId = setTimeout(step, interval);
return () => clearTimeout(timerId);
}
// 使用
const stop = accurateInterval(() => {
console.log(new Date().toISOString());
}, 1000);
// 停止
setTimeout(stop, 5000);