跳到主要内容

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.js1ms
// 即使设置 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);

相关链接