JavaScript 异步编程与 Promise 学习笔记

33 阅读4分钟

JavaScript 异步编程与 Promise 学习笔记

一、JavaScript 的单线程模型

JavaScript 是一门单线程语言,这意味着它在同一时间只能执行一个任务。这种设计初衷是为了简化开发模型,尤其是在浏览器环境中,JS 需要处理用户交互(如点击、滚动)、DOM 更新、网络请求等任务。如果允许多线程并行操作 DOM,将极易引发竞态条件和不可预测的 UI 状态。

1.1 同步 vs 异步代码

  • 同步代码:按照书写顺序从上到下依次执行,每行代码必须等待前一行执行完毕才能继续。例如:

    console.log(1);
    let a = 5;
    for (let i = 0; i < 3; i++) console.log(i);
    console.log(2);
    

    这类操作通常在毫秒(ms)级别完成,属于“阻塞式”执行。

  • 异步代码:不立即执行,而是被放入**事件循环(Event Loop)**中,等到主线程空闲且满足触发条件(如定时器到期、I/O 完成)时才执行。典型例子包括 setTimeout、文件读取、网络请求等。例如:

    console.log(1);
    setTimeout(() => console.log(2), 3000); // 3秒后执行
    console.log(3);
    

    输出结果为:1 → 3 → 2。这说明异步任务不会阻塞后续同步代码的执行。

💡 关键理解:JS 的“单线程 + 事件循环”机制使得它既能保持简单性,又能高效处理 I/O 密集型任务(如网络、文件),而无需多线程的复杂性。


二、Promise:异步流程控制的革命

虽然回调函数(Callback)可以处理异步,但容易陷入“回调地狱”(Callback Hell),代码难以维护。ES6 引入的 Promise 提供了一种更优雅、链式、可组合的异步编程方式。

2.1 Promise 的基本结构

const p = new Promise((resolve, reject) => {
  // 异步操作(立即执行)
  if (/* 成功 */) {
    resolve(value); // 兑现承诺
  } else {
    reject(error);  // 拒绝承诺
  }
});

p.then(result => {
  // 处理成功结果
}).catch(error => {
  // 处理失败错误
});
  • new Promise() 接收一个执行器函数(executor) ,该函数会立即同步执行

  • resolve()reject() 是由 Promise 内部提供的函数,用于改变 Promise 的状态:

    • pending(进行中)fulfilled(已兑现)rejected(已拒绝)
  • .then() 用于注册成功回调,.catch() 用于捕获错误。

2.2 示例:用 Promise 改造 setTimeout

原始异步代码输出顺序为 1 → 3 → 2

console.log(1);
setTimeout(() => console.log(2), 3000);
console.log(3);

若希望按 1 → 2 → 3 顺序执行,可使用 Promise 封装:

console.log(1);

const P = new Promise(resolve => {
  setTimeout(() => {
    console.log(2);
    resolve(); // 通知任务完成
  }, 3000);
});

P.then(() => {
  console.log(3);
});

console.log(4); // 注意:4 仍会先于 3 输出!

⚠️ 注意:console.log(4) 是同步代码,会在 P.then() 之前执行。因此实际输出是:1 → 4 → 2 → 3
若想让 32 后、4 前输出,则需将 4 也放入 .then() 链中。


三、Promise 处理 I/O 操作(Node.js 示例)

在 Node.js 中,文件读取(fs.readFile)是典型的异步 I/O 操作。使用回调会导致嵌套,而 Promise 可使其扁平化。

3.1 回调风格(传统方式)

fs.readFile('./a.txt', (err, data) => {
  if (err) throw err;
  console.log(data.toString());
});

3.2 Promise 封装

console.log(1);

const P = new Promise((resolve, reject) => {
  console.log(3); // 同步执行
  fs.readFile('./b.txt', (err, data) => {
    if (err) {
      reject(err); // 触发 .catch()
      return;
    }
    resolve(data.toString()); // 传递数据给 .then()
  });
});

P.then(data => {
  console.log(data, '/////'); // 成功处理
}).catch(err => {
  console.log(err, '读取文件失败');
});

console.log(2);

执行顺序分析

  1. console.log(1) → 输出 1
  2. 创建 Promise 实例,执行器立即运行 → 输出 3
  3. fs.readFile 是异步,注册回调后立即返回
  4. console.log(2) → 输出 2
  5. 文件读取完成后,根据结果调用 resolvereject
  6. 最终输出文件内容或错误信息

优势:错误处理集中(.catch),逻辑清晰,支持链式调用。


四、Promise 与 Web API:fetch 请求示例

浏览器中的 fetch 返回一个 Promise,天然支持链式处理:

<ul id="members"></ul>
<script>
  fetch('https://api.github.com/orgs/lemoncode/members')
    .then(response => response.json()) // 解析 JSON
    .then(members => {
      const html = members.map(item => `<li>${item.login}</li>`).join('');
      document.getElementById('members').innerHTML = html;
    })
    .catch(err => console.error('请求失败:', err));
</script>

结合知识库中的成员数据,我们甚至可以模拟渲染:

// 假设 members 是从文件加载的数组
const members = [
  { login: "antonio06" },
  { login: "brauliodiez" },
  // ... 其他成员
];

document.getElementById('members').innerHTML = 
  members.map(m => `<li>${m.login}</li>`).join('');

🔗 fetch + Promise 极大简化了 AJAX 编程,避免了 XMLHttpRequest 的繁琐。


五、Promise 的核心价值总结

  1. 状态管理:Promise 有且仅有三种状态(pending/fulfilled/rejected),状态一旦改变不可逆。

  2. 链式调用.then() 返回新 Promise,支持连续异步操作。

    fetch(url)
      .then(res => res.json())
      .then(data => process(data))
      .then(result => save(result));
    
  3. 错误冒泡:任一环节出错,会跳转到最近的 .catch()

  4. 解耦异步逻辑:将“做什么”和“何时做”分离,提升代码可读性与可测试性。


六、常见误区与最佳实践

  • 不要在 Promise 执行器中抛出未捕获异常

    new Promise((resolve, reject) => {
      throw new Error('Oops!'); // 会被 reject 捕获
    }).catch(console.error);
    
  • 总是使用 .catch() :避免未处理的 rejection 导致静默失败。

  • 避免“Promise 嵌套” :应使用链式 .then() 而非在 .then() 内部再建 Promise。

  • 善用 Promise.all() 并行处理多个异步任务

    Promise.all([fetchA(), fetchB(), fetchC()])
      .then(results => console.log('全部完成:', results));
    

结语

Promise 是 JavaScript 异步编程的重要里程碑,它以“承诺”的哲学,将混乱的回调世界变得有序、可控。尽管如今 async/await 已成为更简洁的语法糖,但其底层仍基于 Promise。深入理解 Promise 的机制,是掌握现代 JS 异步编程的基石。正如那句名言:“Don’t callback me, I’ll promise you.