大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。
sleep 或 delay 是一种编程功能,允许开发者将程序或程序的特定部分的执行暂停指定的时间,这在某些情况下很有用,例如:当开发者需要在两个操作之间引入延迟、创建基于时间的循环或同步多线程应用程序中的任务时。
sleep 或 delay 通常采用一个参数,该参数是延迟的持续时间(以秒、毫秒或微秒为单位),具体取决于所使用的编程语言和库。 一旦调用该函数,程序将 sleep 或 delay 指定的时间,然后再继续执行。
在 Node.js 中创建 sleep 或 delay 的方法有多种,具体取决于是否要阻止线程。 Node.js 被设计为异步非阻塞,因此一般不建议阻塞线程。 但是,在某些情况下开发者可能真的需要这样做,例如:调试或测试。
1.使用 setTimeout
创建非阻塞 sleep 的一种方法是使用 setTimeout() 函数,该函数采用回调函数和以毫秒为单位的时间作为参数。 回调函数将在指定时间后执行,但其余代码将继续运行。
开发者可以使用它创建一个返回 Promise 的 sleep 函数,然后将其与 async/await 语法一起使用。 例如:
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function main() {
console.log('Before sleep');
await sleep(1000);
// 等待1s
console.log('After sleep');
}
main();
需要强调的是:await 实际上是暂停函数执行,直到 Promise resolve 或者 reject,然后得到 Promise 结果并恢复执行。 在这期间不会消耗任何 CPU 资源,因为 JavaScript 引擎可以同时执行其他工作,比如:执行其他脚本、处理事件等(注意:JS 是单线程)。
async/await 只是比 Promise.then 更优雅的获取 Promise 结果的语法,同时也更容易阅读和编写。
2.使用 Timers Promise API
这是 Node.js v15.0.0 中引入的新 API,提供了计时器函数的 Promise 版本,例如: setTimeout()、setInterval() 和 setImmediate()。
开发者可以从 timers/promises 模块导入 setTimeout() 函数,并将其与 await 语法一起使用。
// 导入Promise版本的setTimeout
import { setTimeout } from 'timers/promises';
// 将setTimeout于await一起使用
async function main() {
console.log('Before sleep');
await setTimeout(1000);
// 等待1s
console.log('After sleep');
}
main();
3.使用 sleep-promise 包
sleep-promise 包提供了一个返回 Promise 的简单 sleep 函数。开发者可以使用 npm 或 yarn 安装,然后导入它并通过 await 语法使用。
import sleep from 'sleep-promise';
async function main() {
console.log('Before sleep');
await sleep(1000);
// Wait for one second
console.log('After sleep');
}
main();
sleep-promise 的代码非常简单,只有简单的 30 行左右。
const cachedSetTimeout = setTimeout;
function createSleepPromise(timeout, { useCachedSetTimeout }) {
const timeoutFunction = useCachedSetTimeout ? cachedSetTimeout : setTimeout;
// 本质上也是指定时间resolve的Promise
return new Promise((resolve) => {
timeoutFunction(resolve, timeout);
});
}
export default function sleep(timeout, { useCachedSetTimeout } = {}) {
const sleepPromise = createSleepPromise(timeout, { useCachedSetTimeout });
// 如果在Promise链中使用,则传递值
// 支持 const v = await sleep(1000)('hello v2')调用
function promiseFunction(value) {
return sleepPromise.then(() => value);
}
// 常规Promise
promiseFunction.then = (...args) => sleepPromise.then(...args);
promiseFunction.catch = Promise.resolve().catch;
return promiseFunction;
}
为了更好的理解上面的代码,可以看看下面的代码示例,主要是为了说明 await 后面跟随一个常规函数相当于直接调用该函数:
function sayHello(arg) {
console.log('sayHello', arg);
return 'sayHello';
}
async function say() {
const res = await sayHello('rujiu');
console.log('res的值', res);
}
say();
// 输出`sayHello rujiu`和`res的值 sayHello`
4.使用 while 循环
如果确实想在 Node.js 中创建阻塞 sleep 或 delay,也可以使用 while 循环来检查当前时间并等待它达到某个值。 但是,不建议这样做,因为它会消耗 CPU 资源并阻止其他代码运行。
比如下面的代码示例:
const sleep = (millis) => {
var stop = new Date().getTime();
while (new Date().getTime() < stop + millis) {}
};
sleep(1000);
console.log('This printed after about one second');
console.log('This printed second');
5.使用 execSync
可以使用 child_process 模块中的 execSync() 函数,该函数同步执行命令并阻止进程直到完成。
开发者可以使用此函数调用操作系统的 sleep 命令,该命令将执行暂停指定的秒数。 但是,也不建议使用此方法,因为取决于操作系统,并且可能存在安全风险。
const { execSync } = require('child_process');
execSync('sleep 1');
//阻塞线程1s
console.log('This printed after about one second');
console.log('This printed second');
6.使用 Delay.js 库
Delay.js 是由前端大神 sindresorhus 开发的一个优秀的库,用于将 Promise 延迟指定的时间。
下面是基本的使用方法,核心值得关注的点在于使用了 signal:
import delay from 'delay';
const result = await delay(100, { value: '' });
console.log(result);
// 返回值 => ''
Delay.js 的核心代码也很容易理解:
// Delay.js代码
const randomInteger = (minimum, maximum) =>
Math.floor(Math.random() * (maximum - minimum + 1) + minimum);
const createAbortError = () => {
const error = new Error('Delay aborted');
error.name = 'AbortError';
return error;
};
const clearMethods = new WeakMap();
export function createDelay({
clearTimeout: defaultClear,
setTimeout: defaultSet,
} = {}) {
// 不能在这里使用“async”,因为我们需要 Promise 身份。
return (milliseconds, { value, signal } = {}) => {
// TODO: Use `signal?.throwIfAborted()` when targeting Node.js 18.
if (signal?.aborted) {
return Promise.reject(createAbortError());
}
// 如何signal被取消,则直接reject
let timeoutId;
let settle;
let rejectFunction;
const clear = defaultClear ?? clearTimeout;
const signalListener = () => {
clear(timeoutId);
rejectFunction(createAbortError());
};
const cleanup = () => {
// 移除事件
if (signal) {
signal.removeEventListener('abort', signalListener);
}
};
const delayPromise = new Promise((resolve, reject) => {
settle = () => {
cleanup();
resolve(value);
};
rejectFunction = reject;
timeoutId = (defaultSet ?? setTimeout)(settle, milliseconds);
});
// 监听abort事件
if (signal) {
signal.addEventListener('abort', signalListener, { once: true });
}
// 延迟promise
clearMethods.set(delayPromise, () => {
clear(timeoutId);
timeoutId = null;
settle();
});
return delayPromise;
};
}
const delay = createDelay();
export default delay;
// 导出delay
export async function rangeDelay(minimum, maximum, options = {}) {
return delay(randomInteger(minimum, maximum), options);
}
export function clearDelay(promise) {
clearMethods.get(promise)?.();
}
参考资料
https://byby.dev/sleep-in-nodejs
https://github.com/brummelte/sleep-promise
https://javascript.info/async-await
https://m.youtube.com/watch?v=N8ONAZSsx80
https://www.educba.com/javascript-sleep/
https://www.studytonight.com/java-examples/difference-between-wait-and-sleep-in-java