重点回顾之异步编程
一、什么是异步编程
(1)、同步和异步的区别
以下用一个例子来说明同步和异步的区别
传统同步模式(低效阻塞) 流程:顾客进店,服务员全程跟进 —— 帮点餐→送后厨→站在厨房门口等菜做好→端菜给顾客,全程无法接待其他顾客。
问题:若菜品烹饪需 10 分钟,服务员被完全卡住,后面的顾客只能排队干等,餐厅效率极低。
现代异步模式(高效非阻塞) 流程:顾客进店,服务员递扫码点餐码(发起异步任务)后,立刻去接待下一位顾客(执行其他任务);后厨收到订单后独立做菜(处理耗时操作),菜做好后按铃通知(异步任务完成触发回调);服务员听到铃声,再回来给该顾客端菜(处理回调)。
(2)、为什么会有异步编程
在JS中代码是单线程编译的(单线程指的是程序在执行过程中,只有一个执行流(线程) 来依次处理任务,同一时间只能执行一个指令,所有任务都在这个唯一的线程中按顺序排队执行)也就是同步。
很多人会疑惑:JavaScript 是单线程的,但为什么能处理异步操作(如网络请求、定时器)?这并非单线程本身支持并行,而是借助了浏览器 提供的多线程辅助模块和事件循环(Event Loop) 机制,(后面会涉及)。
异步编程是一种编程范式,核心是解决程序中耗时操作(如网络请求、文件读写、数据库查询等)导致的阻塞问题,让程序在等待耗时操作完成的过程中,不闲置而是继续执行其他任务,待耗时操作完成后再回调处理其结果。
二、异步编程的实现方法
回调函数,Promise、async/await、Ajax、
(1)回调函数(最基础)
// 模拟三个依赖的异步任务
function task1(callback) {
setTimeout(() => {
console.log("任务1完成");
callback("任务1的结果");
}, 1000);
}
function task2(data, callback) {
setTimeout(() => {
console.log("任务2完成,依赖:", data);
callback("任务2的结果");
}, 1000);
}
function task3(data, callback) {
setTimeout(() => {
console.log("任务3完成,依赖:", data);
callback("任务3的结果");
}, 1000);
}
// 嵌套调用:回调地狱
task1((res1) => {
task2(res1, (res2) => {
task3(res2, (res3) => {
console.log("所有任务完成,最终结果:", res3);
});
});
});
回调函数是解决异步编程最基础的方法,在调用少的情况下缺陷不明显,但是在回调多的时候就会有很明显的可读性和维护性方法的缺陷——回调地狱。
以上引入了异步编程的基础实现方式,来理解异步的执行逻辑,下面来进行当前异步编程的核心实现方法和编程逻辑。
(2)Promise
-
Promise是ES6正式纳入标准来解决回调地狱的的基础语法。
-
Promise通过链式调用使代码的可读性更强,流程更清晰
promise
.then((result) => {
console.log("成功:", result); // 输出:成功:异步操作成功的结果
})
.catch((error) => {
console.log("失败:", error);
})
.finally(() => {
console.log("异步操作结束(无论成败)");
});
b.Promise的错误处理更清晰
Promise的错误具有冒泡的特性,链式调用中的任何一个环节抛出错误都会传递到最近的catch中,所以一般都会在链式调用的末尾统一捕获错误。
function task1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const isError = false; // 模拟失败:改为 true
if (isError) {
reject("任务1执行失败"); // 触发失败
} else {
console.log("任务1完成");
resolve("任务1的结果");
}
}, 1000);
});
}
// 链式调用 + 统一错误捕获
task1()
.then((res1) => task2(res1))
.then((res2) => task3(res2))
.then((res3) => console.log("最终结果:", res3))
.catch((error) => console.log("错误:", error));
-
Promise的静态方法
1、Promise.all
等待所有的Promise都成功之后返回成功结果,若有一个失败则立即返回失败原因
2、Promise.race
等待第一个状态变更的Promise
const request = new Promise((resolve) => {
setTimeout(() => resolve("接口请求成功"), 3000);
});
const timeout = new Promise((_, reject) => {
setTimeout(() => reject("请求超时(5秒)"), 5000);
});
Promise.race([request, timeout])
.then((res) => console.log(res)) // 输出:接口请求成功
.catch((err) => console.log(err));
3、Promise.resolve
立即创建一个成功的Promise
4、Promise.reject
立即创建一个失败的Promise
5、Promise.allSettled
等待所有的Promise完成(无论是否成功),返回每个Promise的结果对象
const p1 = Promise.resolve(1);
const p2 = Promise.reject("失败了");
Promise.allSettled([p1, p2])
.then((results) => console.log(results));
// 输出:
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: '失败了' }
// ]
6、Promise.any
等待第一个完成的Promise,若都失败则返回一个AggregateError(包含所有失败原因)。
7、.then
处理成功/失败结果
8、.catch
捕获错误,用来处理失败结果
9、.finally
无论Promise是否成功都会执行,常用于清理操作。
(3)async/await(重点掌握)
1、为什么会出现async和await——promise的不足
Promise 解决了回调地狱的嵌套问题,通过链式调用将横向嵌套转为纵向流程,但仍有不足:
-
链式调用过长时,依然会出现「.then 链」,逻辑连贯性不如同步代码;
-
复杂的异步流程(如条件分支、循环)在 Promise 中实现时,代码依然繁琐;
-
异常处理虽有
catch,但多链并行时的异常捕获仍需额外处理。
// Promise 链式调用:仍有链式冗余
fs.promises.readFile('file1.txt')
.then(data1 => {
return fs.promises.readFile('file2.txt').then(data2 => [data1, data2]);
})
.then(([data1, data2]) => {
return fs.promises.readFile('file3.txt').then(data3 => [data1, data2, data3]);
})
.then(([data1, data2, data3]) => {
console.log(data1 + data2 + data3);
})
.catch(err => console.error(err));
代码仍需围绕 then 展开,无法像同步代码一样「线性书写」。
async/await 是 Promise 的语法糖,它的诞生就是为了彻底解决上述问题,核心目标是:用同步代码的书写方式实现异步逻辑,同时保留 Promise 的异步特性和异常处理能力。
async/await 让异步操作的代码不再依赖嵌套或链式调用,而是像同步代码一样按顺序书写,可读性和可维护性大幅提升。
// async/await 线性书写:和同步代码逻辑一致
async function readFiles() {
try {
const data1 = await fs.promises.readFile('file1.txt');
const data2 = await fs.promises.readFile('file2.txt');
const data3 = await fs.promises.readFile('file3.txt');
console.log(data1 + data2 + data3);
} catch (err) {
console.error(err);
}
}
await关键字只能在async内使用,它会暂停函数的执行,直到Promise状态变为fulfilled再继续执行并返回结果。async和await与Primise的静态方法结合,可以实现错误捕获的更便利的方法
// 模拟成功的请求:获取用户信息
function getUserInfo() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: "张三", age: 20 });
}, 1000);
});
}
// 模拟失败的请求:获取用户订单(模拟接口报错)
function getUserOrder() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("获取订单失败:接口超时")); // 模拟错误
}, 1000);
});
}
async function fetchSingleData() {
try {
// 尝试请求失败的接口
const order = await getUserOrder();
console.log("用户订单:", order); // 失败时不会执行
} catch (error) {
// 捕获错误并处理
console.error("单请求错误:", error.message);
} finally {
// 可选:无论成败都会执行,用于清理操作(如关闭加载)
console.log("单请求执行完成(无论成败)\n");
}
}
// 执行函数
fetchSingleData();
// async/await + try/catch(推荐,代码更同步)
async function fetchAllData() {
try {
// 并行请求:一个成功,一个失败
const [userInfo, userOrder] = await Promise.all([
getUserInfo(),
getUserOrder(),
]);
console.log("并行请求结果:", { userInfo, userOrder }); // 失败时不会执行
} catch (error) {
console.error("Promise.all 错误:", error.message);
}
}
//不使用.all方法,代码会更多
async function fetchAllData() {
try {
// 1. 手动启动所有异步任务(并行执行,立即调用函数)
const userInfoPromise = getUserInfo();
const userOrderPromise = getUserOrder();
// 2. 手动等待每个 Promise 完成(效果等价于 Promise.all)
// 注意:这里依然是并行,因为两个 Promise 已经提前启动
const userInfo = await userInfoPromise;
const userOrder = await userOrderPromise;
console.log("并行请求结果:", { userInfo, userOrder });
} catch (error) {
console.error("手动并行错误:", error.message);
}
}
fetchAllData();
三、异步编程的核心机制:事件循环
定义
事件循环(Event Loop)是JavaScript 实现异步编程的核心机制,因为 JS 是单线程语言,无法同时执行多任务,事件循环通过调度任务的执行顺序,让 JS 能够处理异步操作(如网络请求、定时器、DOM 事件),避免单线程阻塞。
事件循环的核心概念
-
执行栈(Call Stack)
-
单线程的「任务执行区」,遵循后进先出(LIFO) 原则。
-
所有同步任务都会被依次压入执行栈中,执行完一个弹出一个,直到栈为空。
-
示例:调用函数时,函数会被压入栈;函数执行完毕,从栈中弹出。
-
-
任务队列(Task Queue)
-
存放异步任务的回调函数的容器,遵循先进先出(FIFO) 原则。
-
异步任务完成后(如定时器到时间、网络请求返回),其回调会被加入任务队列,等待执行。
-
-
异步任务的分类
事件循环中的异步任务被分为两类,执行优先级不同,这是理解事件循环的关键:
-
宏任务(Macrotask):粒度较大的异步任务,每次事件循环仅执行一个宏任务。
常见类型:
-
微任务(Microtask):粒度较小的异步任务,每次宏任务执行完后,会清空所有微任务队列中的任务。

-
核心逻辑
可以总结为「先同步、再微任务、再宏任务」(微任务的优先级大于宏任务)
案例
console.log('同步代码1'); // 同步任务
setTimeout(() => { // 宏任务
console.log('setTimeout 宏任务');
}, 0);
Promise.resolve().then(() => { // 微任务
console.log('Promise 微任务1');
return '微任务返回值';
}).then(res => { // 微任务执行中产生的新微任务
console.log('Promise 微任务2', res);
});
console.log('同步代码2'); // 同步任务
答案
同步代码1
同步代码2
Promise 微任务1
Promise 微任务2 微任务返回值
setTimeout 宏任务
console.log(1)
new Promise((resolve, reject) => {
console.log(2)
resolve()
}).then(res => {
console.log(3)
})
console.log(4)
输出
1,2,4,3
四、异步在浏览器端的使用场景
异步在浏览器的使用场景最常用的为:
1、网络请求
最常见的场景,如通过fetch/axios请求后端接口、加载图片 / 视频 / 脚本等静态资源,必须等待服务端响应或资源加载完成才能处理数据;
2、DOM事件监听
用户交互是异步触发的,如点击 (click)、滚动 (scroll)、输入 (input) 等事件,程序需要异步监听并处理;
3、本地存储操作
读写localStorage/sessionStorage(虽同步 API 为主,但 IndexedDB 为异步)、文件上传 / 下载(通过FileReader读取本地文件);
4、Web API操作(了解)
如Geolocation获取地理位置、Web Worker处理后台计算、WebSocket实时通信。
更多推荐



所有评论(0)