从定时器到 Promise:一次 JS 异步编程的进阶之旅

在学习 JavaScript 的过程中,我们经常会听到"JS 是单线程语言 "、"异步操作 "、"Promise 控制流程 "等概念。它们听起来高深,但其实都是理解 JavaScript 运行机制的关键。本文将从最基础的同步与异步入手,逐步讲清楚 为什么需要异步异步是如何执行的 、以及 Promise 是如何让异步代码更优雅地运行的


一、JS 是单线程语言

JavaScript 是一种单线程 语言,也就是说,它在同一时刻只能执行一段代码。

举个简单的例子:

arduino 复制代码
console.log(1);
console.log(2);
console.log(3);

运行结果非常直观:

rust 复制代码
1 -> 2 ->3

JavaScript 会从上到下、逐行执行 ,这就是 同步执行


二、为什么需要异步?

问题在于:当代码中出现"耗时操作"时,例如:

  • 读取文件(I/O 操作)
  • 发送网络请求(fetch / ajax)
  • 定时器(setTimeout)

如果这些操作是同步的,那么 JS 会卡死等待结果,导致页面无法响应用户点击、滚动等操作。

于是,JavaScript 设计了"异步机制 ":

耗时的任务不阻塞主线程,而是交给浏览器或 Node.js 的底层去处理,等任务完成后再通知 JS 主线程执行回调函数。


三、setTimeout:最经典的异步例子

xml 复制代码
<script>
console.log(1);
setTimeout(() => {
  console.log(2);
}, 1000);
console.log(3);
</script>

执行结果为:

rust 复制代码
1 -> 3 -> 2

解释:

  1. console.log(1) 立即执行。
  2. setTimeout 被识别为一个"异步任务",交给 浏览器的定时器线程 去计时。
  3. JS 主线程继续往下执行 console.log(3)
  4. 当计时结束后,回调函数被放入 事件队列(Event Queue)
  5. 主线程空闲时通过 事件循环(Event Loop) 取出该回调并执行,于是打印 2

四、事件循环(Event Loop)简要说明

事件循环是浏览器或 Node.js 实现异步的核心机制。

  • JS 主线程执行同步代码;
  • 异步任务交给浏览器处理;
  • 当异步任务完成后,其回调函数被放入"任务队列";
  • 一旦主线程空闲,事件循环会取出这些任务并执行。

简单来说:

同步 → 先执行

异步 → 等主线程空闲再执行


五、Promise:让异步更优雅

在早期,异步常常通过**回调函数(callback)**实现,但当多个异步操作需要依次执行时,就会出现"回调地狱":

javascript 复制代码
setTimeout(() => {
  console.log('任务1');
  setTimeout(() => {
    console.log('任务2');
  }, 1000);
}, 1000);

可读性差、难以维护。

为了解决这个问题,ES6 引入了 Promise


六、Promise 的基本使用

看下面的例子:

javascript 复制代码
console.log(1);

const p = new Promise(function(resolve) {
  setTimeout(function() {
    console.log(2);
    resolve();
  }, 3000);
});

p.then(function() {
  console.log(3);
});

console.log(4);

输出结果:

rust 复制代码
1-> 4 -> 2 -> 3

分析执行顺序:

  1. console.log(1):同步执行。
  2. new Promise(...):立即执行里面的函数(称为 executor ),但其中的 setTimeout 是异步操作。
  3. console.log(4):继续执行同步任务。
  4. 3 秒后,setTimeout 的回调执行,打印 2,同时调用 resolve()
  5. resolve() 触发 then 方法中的回调,打印 3

可以看到,Promise 让异步代码的执行顺序更清晰,结构也更优雅。


七、结合 Node.js 的异步 I/O 示例

在 Node.js 中,文件读取就是典型的异步操作:

javascript 复制代码
import fs from 'fs';

console.log(1);

const p = new Promise(function(resolve, reject) {
  console.log(3); // 同步执行
  fs.readFile('./b.txt', (err, data) => {
    if (err) {
      reject(err);
      return;
    }
    resolve(data.toString());
  });
});

p.then(function(data) {
  console.log(data, '//////');
}).catch(function(err) {
  console.log(err, '读取文件失败');
});

console.log(2);

执行顺序:

rust 复制代码
1 -> 3 -> 2 -> <文件内容> ///////

解释:

  • fs.readFile 是 Node 的异步 I/O 操作;
  • 读取文件的任务被交给系统底层;
  • 主线程不会等待;
  • 当读取完成后,通过事件循环回到主线程执行 .then()

八、fetch 与异步请求

浏览器端的 fetch 也是基于 Promise 的异步操作。

xml 复制代码
<script>
console.log(fetch('https://api.github.com/orgs/lemoncode/members'));
</script>

🧠fetch() 返回的是一个 Promise 对象。我们可以使用 .then() 来获取返回的数据:

ini 复制代码
fetch('https://api.github.com/orgs/lemoncode/members')
  .then(res => res.json())
  .then(data => {
    document.getElementById('member').innerHTML =
      data.map(item => `<li>${item.login}</li>`).join('');
  });

九、总结

概念 含义 示例
同步代码 从上到下顺序执行 console.log()
异步代码 延后执行,不阻塞主线程 setTimeoutfetchfs.readFile
事件循环 调度同步和异步任务 异步任务完成后放入任务队列等待执行
Promise 用于优雅地处理异步 .then().catch()

🧩一句话总结:

JavaScript 虽然是单线程的,但通过事件循环和 Promise 等机制,实现了强大的异步能力,让我们能够同时处理多个任务,而不影响页面或程序的流畅运行。

相关推荐
threelab2 小时前
Merge3D:重塑三维可视化体验的 Cesium+Three.js 融合引擎
开发语言·javascript·3d
R.lin2 小时前
MyBatis 专题深度细化解析
oracle·面试·mybatis
Mintopia3 小时前
🌐 跨模态迁移学习:WebAIGC多场景适配的未来技术核心
前端·javascript·aigc
绝无仅有3 小时前
Redis 面试题解析:某度互联网大厂
后端·面试·架构
绝无仅有3 小时前
某度互联网大厂 MySQL 面试题解析
后端·面试·架构
无心水3 小时前
【中间件:Redis】3、Redis数据安全机制:持久化(RDB+AOF)+事务+原子性(面试3大考点)
redis·中间件·面试·后端面试·redis事务·redis持久化·redis原子性
乄夜4 小时前
嵌入式面试高频!!!C语言(十四) STL(嵌入式八股文)
c语言·c++·stm32·单片机·mcu·面试·51单片机
艾小码4 小时前
别再只会用默认插槽了!Vue插槽这些高级用法让你的组件更强大
前端·javascript·vue.js
九年义务漏网鲨鱼4 小时前
【机器学习算法】面试中的ROC和AUC
算法·机器学习·面试