异步编程三剑客:回调、Promise 与 Async/Await 的奇幻冒险
文章深入浅出地解析了 JavaScript 异步编程的演进:从易嵌套的回调函数,到支持链式调用的 Promise,再到语法简洁、逻辑清晰的 Async/Await,展现了异步处理从混乱到优雅的蜕变历程。
引言
想象一下,你正在厨房煮一碗泡面。你把水烧上,然后站在灶台前干等——直到水开了才放面。这叫“同步”:一步做完再做下一步。
但现实生活中,我们更聪明:水一烧上,就去刷牙、回消息、逗猫……等水开了再回来。这就是“异步”——不阻塞主线程,高效利用时间。
在 JavaScript 的世界里,异步编程经历了三次“进化”:回调函数 → Promise → Async/Await。今天,我们就用两段代码,带你穿越这场异步革命的奇幻旅程!
第一幕:回调函数 —— “信任的崩塌”
先看这段 Node.js 代码:
// 使用 Node.js 内置的 fs 模块异步读取文件 './1.html'
// 第三个参数是一个回调函数,当文件读取完成(或出错)时被调用
fs.readFile('./1.html', 'utf-8', (err, data) => {
// 如果发生错误(例如文件不存在),err 会被赋值,此时提前返回并打印错误
if (err) {
console.log(err);
return;
}
// 文件成功读取,data 包含文件内容(字符串形式)
console.log(data);
// 打印标记,用于观察执行顺序
console.log(111);
})
这是经典的回调函数写法:你告诉 readFile:“文件读完后,请调用这个函数。”
听起来很美好?但问题来了:
- 如果要连续读三个文件?
- 每个操作都依赖上一个的结果?
- 错误处理层层嵌套?
于是,回调地狱(Callback Hell) 诞生了——代码向右无限缩进,像一座歪斜的塔,随时倒塌。
回调的本质:把“下一步做什么”作为参数传进去。
痛点:控制流混乱、错误难追踪、无法用 try/catch。
第二幕:Promise —— “契约精神的崛起”
为了解决回调的混乱,ES6 带来了 Promise。它像一份“承诺”:“我保证未来会给你一个结果,要么成功(resolve),要么失败(reject)。”
看我们的主角登场:
// 创建一个新的 Promise 实例
// executor 函数接收两个参数:resolve(成功时调用)和 reject(失败时调用)
const p = new Promise((resolve, reject) => {
// 在 Promise 内部调用异步操作 fs.readFile
fs.readFile('./1.html', 'utf-8', (err, data) => {
// 如果读取文件出错,调用 reject 将 Promise 状态变为 rejected
if (err) {
reject(err);
return;
}
// 成功读取文件,调用 resolve 将 Promise 状态变为 fulfilled,并传递数据
resolve(data);
// 打印标记,用于观察执行时机(注意:此行在 resolve 之后,但仍会执行)
console.log(111);
// 再次调用 resolve —— 但无效!Promise 状态一旦确定,无法再次更改
resolve(data); // 注意:多次 resolve 无效!
})
})
这里,我们把异步操作包装成一个 Promise 对象。外部可以通过 .then() 接收成功结果,.catch() 捕获错误。
而更妙的是,Promise 支持链式调用:
// 当 Promise p 变为 fulfilled 状态时,.then 中的回调会被调用
// 参数 data 即为 resolve(data) 中传入的值
p.then(data => {
console.log(data); // 打印文件内容
console.log(111); // 打印标记,观察执行顺序
})
✅ Promise 的优势:
- 链式调用,避免嵌套
- 统一错误处理
- 状态一旦确定(fulfilled/rejected),不可更改
但…….then().then().catch() 写多了,还是有点啰嗦,不够“同步感”。
Promise详解:Promise:让 JavaScript 异步任务“同步化”的利器-CSDN博客
第三幕:Async/Await —— “异步即同步”的魔法
终于,ES2017(ES8)带来了终极武器:Async / Await!
它让异步代码看起来像同步代码,却丝毫不阻塞主线程。简直是开发者的白月光!
场景一:浏览器中的 GitHub API 调用
// 使用 async 关键字声明一个异步函数
// async 函数总是返回一个 Promise
const main = async () => {
// await 会暂停函数执行,直到 fetch 返回的 Promise 被 fulfilled
// res 是 fetch 成功后的 Response 对象
const res = await fetch('https://api.github.com/users/shunwuyu/repos')
console.log(res); // 打印原始响应对象
console.log(111); // 标记执行顺序
// res.json() 也返回一个 Promise,需再次 await 获取解析后的 JSON 数据
const data = await res.json();
console.log(data); // 打印解析后的仓库数据数组
}
// 调用异步函数(不会阻塞后续代码)
main();
看!没有 .then,没有回调,只有 await —— “等这个 Promise 完成,再继续”。
场景二:Node.js 中读取文件
// 同样使用 async/await 处理之前定义的 Promise p
const main = async () => {
// await 等待 p 这个 Promise 完成
// 如果 p 成功,html 就是 resolve 传入的文件内容
const html = await p;
console.log(html); // 打印文件内容
console.log(111); // 标记执行顺序
}
main();
同一个 main 函数,既能在浏览器跑,也能在 Node.js 跑!Async/Await 是跨环境的异步语法糖。
✨ Async/Await 的三大魅力:
- 代码线性可读:从上到下,逻辑清晰
- 天然支持 try/catch:错误处理回归传统
- 底层仍是 Promise:兼容所有 Promise API
彩蛋:为什么 resolve(data) 写了两次?
眼尖的朋友可能注意到:
resolve(data);
console.log(111);
resolve(data); // ← 这行多余!
Promise 一旦 resolve,状态就锁定,后续的 resolve 或 reject 都会被忽略。所以第二行是无效的——但不会报错。这是 Promise 的“不可逆”特性,确保状态一致性。
总结:异步编程的进化树
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 回调函数 | 简单直接 | 回调地狱、错误难处理 | 简单单次异步操作 |
| Promise | 链式调用、状态管理清晰 | 语法稍显冗长 | 复杂异步流程 |
| Async/Await | 同步风格、易读易维护 | 必须在 async 函数中使用 | 现代项目首选 |
结语
从回调的信任危机,到 Promise 的契约精神,再到 Async/Await 的魔法降临——JavaScript 的异步编程,正变得越来越优雅、强大。
而你手中的这两段代码,正是这场变革的缩影。
下次当你写下 async 和 await 时,请记住:你不是在写代码,你是在指挥时间——让异步的世界,为你同步运转。
🌟 记住:
回调是过去,Promise 是现在,Async/Await 是未来。
而你,正站在未来的起点。
源码
1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// fetch('https://api.github.com/users/shunwuyu/repos')
// .then(res=> {
// //console.log(res.json())
// return res.json()
// }) //函数
// .then(data=>{
// console.log(data)
// })
//es8 async 修饰函数
const main = async ()=>{
// await等待右边的promise执行完成
// resolved resolve(data) 交给左边的变量
const res = await fetch('https://api.github.com/users/shunwuyu/repos')
console.log(res);
console.log(111);
const data = await res.json();
console.log(data);
}
main();
</script>
</body>
</html>
2.mjs
import fs from 'fs';
// es6 之前,回调函数
// fs.readFile('./1.html', 'utf-8', (err, data) => {
// if (err) {
// console.log(err);
// return;
// }
// console.log(data);
// console.log(111);
// })
const p = new Promise((resolve, reject) => {
fs.readFile('./1.html', 'utf-8', (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
console.log(111);
resolve(data);
})
})
// p.then(data => {
// console.log(data);
// console.log(111);
// })
const main = async () => {
const html = await p;
console.log(html);
console.log(111);
}
main();
更多推荐


所有评论(0)