JS異步:setTimeout包裝為sleep

前言

最近在开发一个浏览器自动化脚本时,遇到了一个看似简单却很重要的异步编程问题:为什么不能直接使用 `setTimeout`,而需要将其包装成一个 `sleep` 函数?这个问题的核心,其实涉及到 JavaScript 的异步编程模型和 Promise 机制。本文将从小白的视角,深入浅出地解释这个概念。

问题场景

在编写自动化脚本时,我们经常需要模拟人类的操作行为,比如:

  1. 输入文本时,需要逐字输入,每个字符之间有延迟

  2. 填写表单时,需要等待一个字段填写完成后再填写下一个

  3. 点击按钮后,需要等待页面响应

最开始的代码可能是这样写的:

复制代码
function fillForm() {
    el1.value = '姓名';      // 立即执行
    setTimeout(() => {
        el2.value = '电话';  // 1秒后执行
    }, 1000);
    el3.value = '地址';      // 立即执行(不会等待上面的 setTimeout)
}

问题出现了:虽然 `setTimeout` 会在 1 秒后执行 `el2.value = '电话'`,但是 `el3.value = '地址'` 不会等待,而是立即执行。这导致三个字段几乎同时被填充,失去了模拟人类操作的效果。

setTimeout 的本质

`setTimeout` 是 JavaScript 提供的一个原生 API,它的作用是:**在指定时间后执行回调函数,但不会阻塞后续代码的执行**。

复制代码
const timerId = setTimeout(() => {
    console.log('1秒后执行');
}, 1000);

console.log('立即执行');
console.log('timerId:', timerId);  // 输出:timerId: 1(一个数字)

// 执行顺序:
// 1. "立即执行" 
// 2. "timerId: 1"
// 3. (1秒后) "1秒后执行"关键点在于:**`setTimeout` 返回的是一个定时器 ID(数字),而不是 Promise**。这意味着它无法与 `await` 配合使用,因为 `await` 只能等待 Promise 对象。

Promise 是什么?

Promise 是 JavaScript 中处理异步操作的一种机制。可以把它想象成一张"欠条",表示"将来某个时刻会给你结果"。

Promise 有三种状态:

  • **pending(等待中)**:操作还在进行,结果未知

  • **fulfilled(已完成)**:操作成功完成

  • **rejected(已拒绝)**:操作失败

    // 创建一个 Promise
    const myPromise = new Promise((resolve, reject) => {
    // resolve = 成功时调用
    // reject = 失败时调用

    复制代码
      setTimeout(() => {
          resolve('操作成功!');  // 1秒后调用 resolve,表示成功
      }, 1000);

    });

await 的作用

`await` 是配合 `async` 函数使用的关键字,它的作用是:**等待 Promise 完成,然后继续执行后续代码**。

复制代码
// 正确示例:使用 await
async function fillForm() {
    el1.value = '姓名';
    
    await sleep(1000);  // 等待 1 秒,代码在这里暂停
    
    el2.value = '电话';  // 1秒后才执行
    el3.value = '地址';  // 在 el2 之后执行
}

关键在于:**`await` 会"阻塞"代码执行**,直到 Promise 完成。这里的"阻塞"是指在当前函数内暂停,不会影响页面的其他操作。

sleep 函数的实现

理解了 Promise 和 await 后,我们就能理解为什么要包装 `setTimeout` 了:

const sleep = ms => new Promise(r => setTimeout(r, ms));这行代码做了什么?

  1. 创建一个新的 Promise

  2. 在 Promise 内部调用 `setTimeout`

  3. 当 `setTimeout` 的回调执行时,调用 `resolve`(这里的 `r` 就是 `resolve`)

  4. 返回这个 Promise,这样就能用 `await` 等待了

简单来说:**`sleep` 将不返回 Promise 的 `setTimeout`,包装成了返回 Promise 的函数**。

对比:setTimeout vs sleep

|----------|--------------|-------------|
| 特性 | setTimeout | sleep (包装后) |
| 返回值 | 数字(timer ID) | Promise |
| 能否 await | 不能 | 能 |
| 是否阻塞执行 | 不阻塞 | 阻塞(在当前函数内) |
| 适用场景 | 延迟执行独立任务 | 需要顺序执行的异步操作 |

相关推荐
wuyk5557 分钟前
第2章:六步换相原理全解+STM32工程实战
c语言·开发语言·stm32·单片机·嵌入式硬件
hey_sml14 分钟前
JAVA每日一学---CompletableFuture中thenApply与thenCompose区别详解
java·开发语言
萧瑟余晖2 小时前
Java深入解析篇十七之SpringCloud
java·开发语言
ttod_qzstudio3 小时前
Java 常用语法极简通关(五):类与对象——字段、方法、构造器、this 与 static
java·开发语言·python
萧瑟余晖4 小时前
Java深入解析篇十七之Spring Security
java·开发语言·spring
avi91114 小时前
[AI教做人]AI平台做2项目;一个3D模型展示,另一个框架多人
javascript·人工智能·ai·3d模型·3d引擎·顶点和法线
kyriewen4 小时前
我用Claude Code两天干完了团队两周的排期——周报发出去那一刻我就后悔了
前端·javascript·ai编程
用户938515635075 小时前
手写一个 LLM Harness 框架:用工程化手段把大模型幻觉踩在脚下
javascript·人工智能·后端
油丶酸萝卜别吃6 小时前
jquery-ajax.js 说明文档
前端·javascript·jquery
windliang6 小时前
Claude Code 源码分析(九):子 Agent 如何分叉、继续与回到父会话
前端·javascript·面试