引言

想象一下,你正在厨房煮一碗泡面。你把水烧上,然后站在灶台前干等——直到水开了才放面。这叫“同步”:一步做完再做下一步。

但现实生活中,我们更聪明:水一烧上,就去刷牙、回消息、逗猫……等水开了再回来。这就是“异步”——不阻塞主线程,高效利用时间。

在 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 的三大魅力

  1. 代码线性可读:从上到下,逻辑清晰
  2. 天然支持 try/catch:错误处理回归传统
  3. 底层仍是 Promise:兼容所有 Promise API

彩蛋:为什么 resolve(data) 写了两次?

眼尖的朋友可能注意到:

resolve(data);
console.log(111);
resolve(data); // ← 这行多余!

Promise 一旦 resolve,状态就锁定,后续的 resolvereject 都会被忽略。所以第二行是无效的——但不会报错。这是 Promise 的“不可逆”特性,确保状态一致性。


总结:异步编程的进化树

方案 优点 缺点 适用场景
回调函数 简单直接 回调地狱、错误难处理 简单单次异步操作
Promise 链式调用、状态管理清晰 语法稍显冗长 复杂异步流程
Async/Await 同步风格、易读易维护 必须在 async 函数中使用 现代项目首选

结语

从回调的信任危机,到 Promise 的契约精神,再到 Async/Await 的魔法降临——JavaScript 的异步编程,正变得越来越优雅、强大。

而你手中的这两段代码,正是这场变革的缩影。

下次当你写下 asyncawait 时,请记住:你不是在写代码,你是在指挥时间——让异步的世界,为你同步运转。

🌟 记住
回调是过去,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();

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐