个人空间:https://blog.csdn.net/m0_73589512
https://blog.csdn.net/m0_73589512
大家好,我是 叁佰万**~~**
如果本文对您有帮助,请献上你的小❥(^_-)哟~~~
作为前端开发者,我们每天都在和 JavaScript 的异步特性打交道 ------ 定时器、网络请求、事件回调...... 但你真的理解 JS 单线程下的异步底层逻辑吗?本文将从进程线程基础讲起,一步步拆解 Event Loop、宏微任务、Promise 核心原理,帮你彻底搞懂 JS 异步编程。
目录
[二、核心机制:Event Loop(事件循环)](#二、核心机制:Event Loop(事件循环))
[2.1 同步与异步的本质](#2.1 同步与异步的本质)
[2.2 宏任务与微任务](#2.2 宏任务与微任务)
[Event Loop 完整执行流程](#Event Loop 完整执行流程)
[3.1 Promise 核心特性](#3.1 Promise 核心特性)
[3.2 Promise 常用静态方法](#3.2 Promise 常用静态方法)
[3.3 手写 Promise(核心实现)](#3.3 手写 Promise(核心实现))
一、基础认知:进程与线程
要理解 JS 异步,首先得搞清楚「进程」和「线程」这两个基础概念,这是浏览器实现异步的硬件底层支撑。
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 通俗类比 | 工厂里的独立车间 | 车间里的工人 |
| 核心定位 | CPU 资源分配的最小单位 | CPU 调度执行的最小单位 |
| 成本特性 | 创建 / 销毁成本高(重量级) | 创建 / 销毁成本低(轻量级) |
| 前端示例 | 浏览器每新开一个标签页 = 新进程 | 浏览器进程内包含多线程:GUI 渲染线程、JS 引擎线程、定时器线程、异步 HTTP 请求线程、事件触发线程 |
这里有个关键知识点:JavaScript 是单线程脚本语言,但浏览器是多线程、多进程的。JS 的单线程意味着它同一时间只能做一件事,而浏览器的多线程则为 JS 实现异步提供了基础。
二、核心机制:Event Loop(事件循环)
JS 单线程要处理同步和异步任务,全靠 Event Loop 这个核心机制。我们先拆解它的执行逻辑。
2.1 同步与异步的本质
- 同步代码 :按顺序执行,会阻塞后续代码(比如普通的
console.log、变量声明); - 异步代码 :不阻塞主线程,进入「任务队列」等待执行(比如
setTimeout、网络请求)。
举个简单例子:
// 同步代码:顺序执行
console.log('1');
console.log('2');
// 异步代码:先执行同步,再执行异步
console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
// 输出:1 → 3 → 2
2.2 宏任务与微任务
异步任务并非「一视同仁」,而是分为宏任务 和微任务,执行优先级有明确区分:
- 宏任务 :脚本代码本身、
setTimeout/setInterval、I/O 操作、UI 渲染等; - 微任务 :
Promise.then/catch/finally、queueMicrotask、Node.js 的process.nextTick等。
Event Loop 完整执行流程
- 执行调用栈中的同步代码(宏任务的一部分);
- 执行完同步代码后,清空所有微任务队列(按顺序执行);
- 如有需要,执行 UI 渲染;
- 从宏任务队列中取出一个宏任务执行;
- 重复步骤 2-4,形成「事件循环」。
核心原则:先同步,再微任务,最后宏任务;每次执行完一个宏任务,都要清空微任务队列。
三、异步解决方案:Promise
早期异步编程依赖嵌套回调函数,容易形成「回调地狱」(金字塔形代码、错误处理困难、复用性差),而 Promise 是解决回调地狱的标准方案。
3.1 Promise 核心特性
-
三种状态:
pending:默认状态(等待中);fulfilled:成功状态(不可逆);rejected:失败状态(不可逆)。状态只能从pending转为fulfilled或rejected,一旦改变就无法修改。
-
链式调用 :Promise 的
then方法会返回新的 Promise,支持链式调用,替代嵌套回调:// 模拟异步请求 function fetchUser(id) { return new Promise(resolve => { setTimeout(() => resolve({ id, name: `用户${id}` }), 1000); }); } function fetchPosts(userId) { return new Promise(resolve => { setTimeout(() => resolve([`帖子1-${userId}`, `帖子2-${userId}`]), 1000); }); } // 链式调用:替代嵌套 fetchUser(1) .then(user => { console.log('用户:', user); return fetchPosts(user.id); // 返回新Promise }) .then(posts => console.log('帖子:', posts)) .catch(error => console.error('错误:', error));
3.2 Promise 常用静态方法
Promise 提供了多个静态方法,满足不同的异步场景需求:
Promise.all:所有 Promise 都成功才返回成功,一个失败则整体失败;Promise.race:多个 Promise 中「最快完成」的那个决定结果(无论成功 / 失败);Promise.allSettled:等待所有 Promise 完成(无论成功 / 失败),返回每个 Promise 的结果;Promise.any:只要有一个 Promise 成功就返回成功,全部失败才返回失败。
示例代码:
// Promise.all:批量请求
Promise.all([fetchUser(1), fetchUser(2)])
.then(users => console.log('所有用户:', users))
.catch(err => console.error('单个失败:', err));
// Promise.race:超时控制
Promise.race([
fetchUser(1),
new Promise((_, reject) => setTimeout(() => reject('请求超时'), 800))
])
.then(user => console.log('请求成功:', user))
.catch(err => console.error('请求失败:', err));
3.3 手写 Promise(核心实现)
理解 Promise 最好的方式是手写一个简易版,核心逻辑包括状态管理、回调队列、then 方法链式调用:
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class MyPromise {
constructor(executor) {
this.status = PENDING; // 默认状态
this.value = undefined; // 成功值
this.reason = undefined; // 失败原因
this.onResolvedCallbacks = []; // 成功回调队列
this.onRejectedCallbacks = []; // 失败回调队列
// 成功回调
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn());
}
};
// 失败回调
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject); // 立即执行执行器
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
// 处理默认回调
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e };
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise2;
}
// 解析Promise返回值(核心逻辑)
resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('循环引用'));
}
let called = false;
if (x instanceof MyPromise) {
x.then(y => this.resolvePromise(promise2, y, resolve, reject), reject);
} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
}, e => {
if (called) return;
called = true;
reject(e);
});
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
四、语法糖:async/await
async/await是 Promise 的语法糖,它让异步代码看起来像同步代码,底层依然基于 Promise 实现。
核心特点
async函数:声明后返回值自动包装为 Promise;await关键字:只能在 async 函数内使用,等待 Promise 完成并返回结果;- 错误处理:可通过
try/catch捕获异步错误,更符合同步代码的写法。
示例:
async function fetchData() {
try {
const user = await fetchUser(1);
console.log('用户:', user);
const posts = await fetchPosts(user.id);
console.log('帖子:', posts);
return posts;
} catch (error) {
console.error('请求失败:', error);
}
}
fetchData();
五、总结
JavaScript 的异步编程体系可以总结为:
- 底层支撑:浏览器多进程 / 多线程为 JS 单线程提供异步能力;
- 核心机制:Event Loop 通过宏任务 / 微任务队列实现异步调度;
- 解决方案:Promise 解决回调地狱,async/await 让 Promise 写法更优雅。
理解这些核心概念,不仅能帮你搞定面试中的异步高频题,更能让你在实际开发中精准定位异步相关的 bug(比如定时器误差、宏微任务执行顺序问题)。异步是 JS 的核心,也是前端进阶的必经之路,希望这篇文章能帮你建立清晰的知识体系。