JavaScript: JavaScript 异步编程

JavaScript 是单线程语言——同一时刻只能做一件事。但如果网络请求要等 3 秒,难道页面就冻住 3 秒?当然不行。异步编程就是解决这个问题的:有些事不用干等着,先拿个号,等好了再来取。就像餐厅叫号——你不用在厨房门口站着,坐回座位等叫号就行。


1. 同步 vs 异步

同步:代码一行一行执行,前一行没完成,后一行就不动。就像排队——前面的人没办完,你就等着。

异步:某些操作(网络请求、定时器、文件读取)启动后不阻塞后续代码,等操作完成后再通过回调/Promise 通知你。

HTML
<script>
console.log('1');
setTimeout(function() {
  console.log('2');
}, 1000);
console.log('3');
</script>

输出顺序是 1 → 3 → 2setTimeout 是异步的,1 秒后才执行回调,不会阻塞 console.log('3')



▶ 示例

JAVASCRIPT
// 本节代码示例
const message = "你好,世界!";
console.log(message);
▶ 试一试

这是最简单的示例,后续章节会详细解释。## 2. 回调函数

回调函数是最原始的异步方式——把一个函数当作参数传进去,异步操作完成后调用它。

▶ 示例:回调基本用法

HTML
<div id="output" style="padding: 10px; border: 1px solid #ccc;"></div>

<script>
const output = document.getElementById('output');

function fetchData(callback) {
  output.textContent = '加载中...';
  setTimeout(function() {
    callback('数据加载完成!');
  }, 2000);
}

fetchData(function(data) {
  output.textContent = data;
});
</script>
▶ 试一试

(1) 回调地狱

当异步操作一个接一个依赖时,回调会层层嵌套——这就是臭名昭著的"回调地狱"(Callback Hell)。

HTML
<script>
getUser(function(user) {
  getOrders(user.id, function(orders) {
    getOrderDetail(orders[0].id, function(detail) {
      getItems(detail.itemId, function(items) {
        console.log('嵌套4层了,再复杂点就根本看不懂');
      });
    });
  });
});
</script>
💡 提示: 回调地狱的代码像"横着的金字塔",难以阅读和维护。Promise 就是来拯救这个问题的。



3. Promise

Promise 是一个代表异步操作最终结果的对象。它有三种状态:

状态 说明
pending 进行中,还没结果
fulfilled 已成功,拿到结果
rejected 已失败,拿到错误

状态一旦从 pending 变成 fulfilledrejected,就不会再变——像射出去的箭,收不回来。

(1) 创建 Promise

HTML
<script>
const promise = new Promise(function(resolve, reject) {
  // 异步操作
  // 成功时调用 resolve(结果)
  // 失败时调用 reject(错误)
});
</script>

(2) then / catch / finally

HTML
<script>
promise
  .then(function(result) { console.log(result); })   // 成功时执行
  .catch(function(error) { console.error(error); })    // 失败时执行
  .finally(function() { console.log('完成'); });      // 无论成功失败都执行
</script>

▶ 示例:Promise 模拟请求

HTML
<button id="loadBtn">加载数据</button>
<div id="output" style="padding: 10px; border: 1px solid #ccc; margin-top: 10px;"></div>

<script>
const btn = document.getElementById('loadBtn');
const output = document.getElementById('output');

function fetchUser() {
  return new Promise(function(resolve, reject) {
    output.textContent = '加载中...';
    setTimeout(function() {
      const success = true;
      if (success) {
        resolve({ name: '张三', age: 25 });
      } else {
        reject(new Error('加载失败'));
      }
    }, 1500);
  });
}

btn.addEventListener('click', function() {
  fetchUser()
    .then(function(user) {
      output.textContent = '姓名: ' + user.name + ', 年龄: ' + user.age;
    })
    .catch(function(err) {
      output.textContent = '错误: ' + err.message;
    })
    .finally(function() {
      console.log('请求完成(无论成功失败)');
    });
});
</script>
▶ 试一试

(3) 链式调用

then 返回的也是 Promise,所以可以链式调用——回调地狱变成了直线。

HTML
<script>
fetchUser()
  .then(function(user) { return fetchOrders(user.id); })
  .then(function(orders) { return fetchDetail(orders[0].id); })
  .then(function(detail) { console.log(detail); })
  .catch(function(err) { console.error(err); });
</script>
💡 提示: 链式调用的 catch 会捕获整条链上任何一步的错误,不用每步都写 catch



4. async / await

async/await 是 Promise 的语法糖——让异步代码看起来像同步代码,可读性大幅提升。

▶ 示例:async/await 改写上面的请求

HTML
<button id="loadBtn">加载数据</button>
<div id="output" style="padding: 10px; border: 1px solid #ccc; margin-top: 10px;"></div>

<script>
const btn = document.getElementById('loadBtn');
const output = document.getElementById('output');

function fetchUser() {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve({ name: '李四', age: 30 });
    }, 1500);
  });
}

btn.addEventListener('click', async function() {
  try {
    output.textContent = '加载中...';
    const user = await fetchUser();
    output.textContent = '姓名: ' + user.name + ', 年龄: ' + user.age;
  } catch (err) {
    output.textContent = '错误: ' + err.message;
  } finally {
    console.log('请求完成');
  }
});
</script>
▶ 试一试
💡 提示: 对比回调写法,async/await 就像把"拿号等叫"变成了"排队但不用离开"——代码流程是直的,可读性直线上升。

(1) 错误处理

async/awaittry/catch 处理错误,和同步代码的错误处理方式一致。

HTML
<script>
async function loadAll() {
  try {
    const user = await fetchUser();
    const orders = await fetchOrders(user.id);
    console.log(orders);
  } catch (err) {
    console.error('出错了:', err);
  }
}
</script>


5. Promise.all 与 Promise.race

方法 说明
Promise.all([p1, p2, p3]) 全部成功才成功,一个失败就失败
Promise.race([p1, p2, p3]) 谁先完成就用谁的结果(无论成败)

▶ 示例:Promise.all 并发请求

HTML
<button id="loadAll">并发加载</button>
<div id="output" style="padding: 10px; border: 1px solid #ccc; margin-top: 10px;"></div>

<script>
const btn = document.getElementById('loadAll');
const output = document.getElementById('output');

function delay(ms, value) {
  return new Promise(function(resolve) {
    setTimeout(function() { resolve(value); }, ms);
  });
}

btn.addEventListener('click', async function() {
  output.textContent = '加载中...';

  const results = await Promise.all([
    delay(1000, '用户数据'),
    delay(1500, '订单数据'),
    delay(800, '配置数据')
  ]);

  output.textContent = '全部加载完成:\n' + results.join(' | ');
});

const raceBtn = document.createElement('button');
raceBtn.textContent = '竞速加载';
document.body.appendChild(raceBtn);

raceBtn.addEventListener('click', async function() {
  const fastest = await Promise.race([
    delay(1000, '源A(1秒)'),
    delay(500, '源B(0.5秒)'),
    delay(800, '源C(0.8秒)')
  ]);
  output.textContent = '最快到达: ' + fastest;
});
</script>
▶ 试一试
💡 提示: Promise.all 像团购——所有人到齐才开饭;Promise.race 像赛跑——谁先到谁赢。多个请求互不依赖时用 all 并发提速,多个镜像源时用 race 选最快的。


❓ 常见问题

Q await 能在普通函数里用吗?
A 不能。await 只能在 async 函数中使用。在普通函数中写 await 会报语法错误。顶层 await 在模块中是支持的,但浏览器兼容性有限。
Q Promise.all 中有一个失败了怎么办?
A Promise.all 会立即拒绝,返回第一个失败的错误。其他 Promise 仍会执行但结果被忽略。如果需要"全部完成无论成败",可以用 Promise.allSettled
Q async 函数的返回值是什么?
A async 函数始终返回 Promise。即使你 return 42,实际返回的是 Promise.resolve(42)。所以调用 async 函数后可以用 .then() 接收结果。

📖 小节

📝 作业

  1. 基础:用 Promise + setTimeout 模拟一个 2 秒延时操作,成功后输出"完成"。
  2. 进阶:用 async/await 改写上题,加上 try/catch 错误处理。
  3. 挑战:用 Promise.all 同时发起 3 个不同延时的模拟请求(1秒/2秒/3秒),全部完成后计算总耗时提示用户。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏