专业编程基础技术教程

网站首页 > 基础教程 正文

JS 实现 Thread.sleep 或 Thread.delay 7种核心技术

ccvgpt 2025-04-07 12:28:28 基础教程 1 ℃

家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。

JS 实现 Thread.sleep 或 Thread.delay 7种核心技术

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

Tags:

最近发表
标签列表