为什么 `setTimeout` 会“插队”?JS 事件循环与 Promise 通关笔记

为什么 setTimeout 会"插队"?JS 事件循环与 Promise 通关笔记

摘要 :明明写在后面的 console.log('end'),却比 setTimeout 先执行?这就是 JS 异步的魔力。本文从单线程模型出发,分析了同步/异步任务、事件循环机制,并手写 Promise 版的 sleep,带你彻底理解 JS 的执行流程控制。

📑 目录

  • 一段让你困惑的代码
  • JS 是单线程的,那异步怎么实现?
  • 事件循环:让异步任务"排队"的机制
  • Promise:异步任务控制的终极方案
  • 手写 sleep:用 Promise 封装定时器
  • fetch 也是 Promise:网络请求的异步本质
  • 一点总结
  • 互动讨论

一段让你困惑的代码

打开控制台运行这段代码:

javascript

javascript 复制代码
console.log('start');
setTimeout(() => {
    console.log('222');
}, 1000);
console.log('end');

输出顺序是:start → end → (1秒后)222。

明明 setTimeout 写在 end 前面,为什么它最后才执行?这就是 JS 异步任务的工作方式。


JS 是单线程的,那异步怎么实现?

JavaScript 被设计为单线程语言,原因很简单:多线程操作 DOM 会引发复杂的同步问题。单线程意味着同一时间只能做一件事。

那为什么我们还能同时处理定时器、网络请求、用户点击?因为 JS 引擎会把任务分为两类:

  • 同步任务 :立即执行,阻塞后续代码。例如 console.log、变量赋值、循环。
  • 异步任务 :暂时挂起,等待时机再执行。例如 setTimeout、fetch、事件监听。

javascript

ini 复制代码
let a = 1;   // 同步
let b = 2;   // 同步
let c = 3;   // 同步
console.log(a + b + c); // 同步

这三行赋值是串行执行的,CPU 一个一个处理。而异步任务可以"先跳过",等同步任务完成后再回来处理。


事件循环:让异步任务"排队"的机制

JS 的运行环境(浏览器或 Node)会维护一个事件循环(Event Loop) 。流程如下:

  1. 先执行所有同步任务(主线程的代码)。
  2. 遇到异步任务(如 setTimeout),将其交给对应的模块(如定时器线程)管理,不阻塞主线程。
  3. 同步任务执行完毕后,主线程空闲,开始轮询事件队列。
  4. 检查异步任务是否满足执行条件(如定时器时间到、网络请求返回),若满足,将其回调推入队列,由主线程执行。

这就是为什么 setTimeout 的回调总在同步代码之后执行------即使延迟是 0 毫秒。


Promise:异步任务控制的终极方案

setTimeout 只能做延迟,无法处理成功/失败状态。ES6 引入的 Promise 提供了更优雅的异步控制方式。

javascript

javascript 复制代码
const p = new Promise((resolve, reject) => {
    // 这个函数立即执行
    console.log('222');
    setTimeout(() => {
        // resolve(666); // 成功时调用
        reject("网络错误"); // 失败时调用
    }, 2000);
});

console.log('start');
p.then((data) => {
    console.log(data); // 接收 resolve 传的值
    console.log('end');
}).catch((data) => {
    console.log(data); // 接收 reject 传的值
    console.log('失败');
}).finally(() => {
    console.log('finally'); // 无论成功失败都执行
});

Promise 核心要点:

  • new Promise(executor) 中的 executor 函数是立即执行 的,所以上面代码会先打印 '222'。
  • resolve 和 reject 是手动调用的,用于标记异步任务完成或失败。
  • then 注册成功回调,catch 注册失败回调,finally 不管成败都执行。
  • 如果不调用 resolve 或 reject,then 和 catch 永远不会触发。

手写 sleep:用 Promise 封装定时器

setTimeout 只能延迟执行一段代码,但无法做到"等待一段时间再继续往下走"。利用 Promise 我们可以封装一个 sleep 函数:

javascript

javascript 复制代码
function sleep(t) {
    return new Promise((resolve) => {
        console.log('现在还是同步代码');
        setTimeout(() => {
            resolve(); // 延迟 t 毫秒后,把 Promise 状态变为成功
        }, t);
    });
}

console.log('start');
sleep(2000).then(() => {
    console.log('end');
    console.log('现在是2000毫秒后');
});

执行结果:先打印 'start',然后立即打印 '现在还是同步代码'(因为 executor 是同步的),2 秒后打印 'end' 和 '现在是2000毫秒后'。

sleep 返回一个 Promise,可以配合 await 使用(在 async 函数中),实现类似同步代码的等待效果。


fetch 也是 Promise:网络请求的异步本质

浏览器提供的 fetch API 用于发送网络请求,它底层就是基于 Promise 的。

javascript

javascript 复制代码
console.log('start');
fetch('https://api.deepseek.com/chat/completions', {
    method: 'post',
}).then((data) => {
    console.log('请求成功');
}).catch((err) => {
    console.log(err);
    console.log('请求失败');
});
console.log('end');

输出顺序:start → end → (请求返回后)成功/失败回调。

网络请求是典型的耗时异步任务,不会阻塞页面渲染。用 Promise 的 then/catch 可以清晰处理返回结果或错误。


一点总结

通过这次梳理,我彻底理解了 JS 异步模型的几个关键点:

  1. JS 是单线程,但通过事件循环实现非阻塞 I/O。
  2. 同步任务 立即执行,异步任务先挂起,等同步任务清空后再轮询执行。
  3. 事件循环是异步任务的管理机制,定时器、网络请求、事件都属于它管理。
  4. Promise 解决了回调地狱,提供 resolve/reject、then/catch/finally 的优雅 API。
  5. 手动实现 sleep 展示了如何用 Promise 封装 setTimeout,让异步等待更可控。
  6. fetch 是基于 Promise 的网络请求标准用法。

理解这些,再遇到"先打印 end 再打印 222"的现象就不会疑惑了。


互动讨论

  1. 如果 setTimeout 的延迟是 0 毫秒,它会立即执行吗? 为什么?
  2. Promise 的 executor 函数是同步执行的,那它的异步能力从哪里来?
  3. 手写 sleep 函数中,如果不调用 resolve(),then 里的回调会执行吗?
  4. fetch 请求失败时,会进入 catch,但如果是网络超时,怎么判断?
  5. async/await 是 Promise 的语法糖,你知道它底层是怎么转换的吗?

📌 一点心得:异步是 JS 的核心特性,也是理解前端性能优化的基础。掌握事件循环和 Promise,才能写出可靠的非阻塞代码。

相关推荐
usdoc文档预览44 分钟前
USDOC系列之PPT文件在线编辑-VUE前端实现在线浏览器在线编辑PPT文档
前端·vue.js·编辑器·office365·ppt在线编辑·office文件在线编辑·文件在线编辑
用户2705077725241 小时前
Agent 调工具卡住?3 种超时与取消策略实测对比
前端
LuHang2 小时前
我一行代码没写,把领导派的活做成了公司内部系统😭
前端
明月_清风2 小时前
Meta 开源了一个 AI Native 组件库:Astryx,150+ React 组件,专为 AI Agent 而设计
前端·人工智能·ai编程
用户850869276152 小时前
秒杀系统中 Shell 脚本并发控制 Bug:究竟哪里出了
前端
程序员大阳4 小时前
HTML+CSS+JS对话式实战(3)-活动管理页实现查询、新增、删除API后端接口
javascript·css·html·实战·删除·查询·新增
炫酷的大高个5 小时前
6 亿播放的闪身步,我用前端算出 15 倍重心差
前端·javascript
不可能片场5 小时前
内容运营自动化 配图折行别把词切两半
前端·electron
Lank_M5 小时前
HEIC转JPG统一转sRGB,P3高饱和色就回不来了
前端
补码缩写5 小时前
sharp默认转JPG颜色没错,ICC和EXIF却全没了
前端