JavaScript同步与异步编程从小白到彻底理解
作者:无糖可可果
异步编程是每个使用JavaScript编程的人都会遇到的问题,无论是前端的ajax请求,或是node的各种异步API,这篇文章主要介绍了JavaScript同步与异步编程从小白到彻底理解的相关资料,需要的朋友可以参考下
JavaScript 的同步与异步是前端开发中必知必会的核心概念。本文结合代码示例,从底层原理到实战应用,带你彻底搞懂 JS 的执行机制。
一、先看一个现象:代码不是"从上到下"执行的?
// 1.js
console.log('start');
setTimeout(() => {
console.log('222');
}, 1000);
console.log('end');
// 输出顺序:
// start
// end
// 222 (1秒后)
如果你刚学 JS,可能会困惑:明明 console.log('222') 写在 console.log('end') 前面,为什么 end 先打印出来了?
这就是 同步代码 与 异步代码 的区别。
二、为什么 JavaScript 是单线程的?
// 2.js let a = 1; let b = 2; let c = 3; console.log(a + b + c);
JS 被设计为 单线程 语言。这意味着同一时间只能做一件事。
对比一下:
- C++ / Java 等系统级语言:支持多进程多线程架构,可以同时开 3 个线程分别声明 a、b、c,并发执行,效率高,但编写复杂、容易出死锁等问题。
- JavaScript:故意设计得简单——只有一个主线程。好处是:不用考虑锁、竞争条件,写起来心智负担低。
但问题来了:单线程下,如果一个任务耗时很长(比如网络请求要等 2 秒),整个页面不就"卡死"了吗?
JavaScript 的解决方案是:异步编程 + Event Loop。
三、JS 执行机制:Event Loop(事件循环)
用比喻来理解:
| 角色 | 类比 | 说明 |
|---|---|---|
| 进程 (Process) | 董事长 | 负责分配资源,拥有 PID |
| 主线程 (Thread) | 经理 | JS 只有一个主线程,执行代码 |
| Event Loop | 任务队列 | 暂存异步任务的"候车室" |
整个执行流程是这样的:
1. 主线程启动
2. 同步代码全部快速执行完毕
↓
3. 遇到异步任务(定时器、fetch请求、事件监听...)
→ 放入 Event Loop 队列,跳过,继续往下执行
↓
4. 同步代码全部跑完后
→ 回到 Event Loop 中,按顺序取出异步任务执行用图片化的方式来理解就是:
│ 同步代码 │ 异步代码 (Event Loop) │
│ console.log('start') ────→│ │
│ console.log('end') ────→│ │
│ │ setTimeout 回调 等待中... │
│ 同步代码执行完毕 ────────→│ 执行 setTimeout 回调 │
│ │ console.log('222') │
这就是为什么 1.js 中 end 在 222 之前输出——setTimeout 被丢进了 Event Loop,等同步代码跑完才轮到它。
四、Promise:异步任务控制的最佳机制
4.1 什么是 Promise?
Promise 是 ES6 引入的,专门用来优雅地控制异步任务。
// 3.js
const p = new Promise((resolve, reject) => {
console.log('许诺言'); // ① 立即执行
setTimeout(() => {
// resolve(666); // ② 成功时调用
reject("网络错误"); // ② 失败时调用
}, 2000);
});
p.then((data) => { // ③ resolve 触发
console.log(data);
})
.catch((err) => { // ③ reject 触发
console.log(err);
})
.finally(() => { // ④ 无论如何都会执行
console.log('finally');
});
理解 Promise 的三个关键点:
| 要点 | 说明 |
|---|---|
| executor 立即执行 | new Promise(fn) 中的 fn 是同步执行的,它是耗时任务的"容器" |
| resolve / reject | 两个函数能力,由你在异步任务完成时手动调用 |
| then / catch / finally | 链式回调,分别对应成功、失败、最终 |
4.2 链式调用的美妙之处
Promise 最大的优点是解决了回调地狱。对比一下:
// ❌ 回调地狱
getUser(userId, (user) => {
getOrders(user.id, (orders) => {
getDetails(orders[0].id, (detail) => {
// ...
});
});
});
// ✅ Promise 链式调用
getUser(userId)
.then(user => getOrders(user.id))
.then(orders => getDetails(orders[0].id))
.then(detail => console.log(detail))
.catch(err => console.error(err));
五、实战:fetch 就是 Promise 的应用
<!-- 4.html -->
<script>
console.log('start');
fetch('https://www.baidu.com', {
method: 'post'
}).then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
});
console.log('end');
// 输出:start → end → (请求完成后打印 res)
</script>
fetch 底层就是一个 Promise。所以它天然支持 .then() / .catch() 链式调用。
六、自实现 sleep 函数
JS 没有内置的 sleep() 功能,但我们可以用 Promise 自己封装一个:
<!-- 5.html -->
<script>
function sleep(t) {
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, t);
});
return p;
}
sleep(2000).then(() => {
console.log('2s后再做');
});
</script>
现代写法可以用 async/await 让它更优雅:
function sleep(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
async function demo() {
console.log('开始');
await sleep(2000);
console.log('2s后执行');
}
七、总结:一张图搞定同步异步
┌─────────────────────────────────────────────────────────┐ │ JavaScript 执行流程 │ ├───────────────┬─────────────────────────────────────────┤ │ 同步任务 │ 异步任务 │ │ (主线程直接执行) │ (Event Loop 管理) │ ├───────────────┼─────────────────────────────────────────┤ │ • 变量声明 │ • setTimeout / setInterval │ │ • 循环/判断 │ • fetch / AJAX 请求 │ │ • console.log │ • 事件监听 (click, scroll...) │ │ • Promise │ • Promise.then/catch 的回调 │ │ executor │ │ ├───────────────┴─────────────────────────────────────────┤ │ 执行顺序: │ │ 1. 清空同步任务 │ │ 2. 从 Event Loop 取微任务 (Promise.then/catch) 执行 │ │ 3. 从 Event Loop 取宏任务 (setTimeout/fetch回调) 执行 │ └─────────────────────────────────────────────────────────┘
核心结论就三句话:
- JS 是单线程的——简单安全,但要靠异步来避免阻塞。
- Event Loop 是调度中心——同步代码先跑完,异步任务排队等。
- Promise 是控制异步的王牌——链式调用,告别回调地狱,搭配
async/await更香。
希望这篇文章能帮你建立起 JS 同步异步的完整认知框架!
到此这篇关于JavaScript同步与异步编程从小白到彻底理解的文章就介绍到这了,更多相关JS同步与异步编程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
