【ES6】day37-async-await

作者:mario 发布时间: 2026-08-27 阅读量:8 评论数:0

第三十七天:async/await 与大屏数据加载实战

蜗牛往上爬 · 前端工业可视化学习笔记 第 37 篇
Promise 解决了嵌套,async/await 让异步代码"看起来像同步"——这是现代前端的终极异步形态。今天学 async/await 语法、fetch 真实请求、Promise.all 并发控制,最后完成一个完整的大屏数据加载流程(loading / 并发 / 降级 / 重试)。


目录


一、async 函数基础

// async 函数返回的永远是 Promise
async function getData() {
  return 42;
}
getData().then(v => console.log(v));  // 42(自动包装)

// await 等待 Promise 完成并拿到结果
async function main() {
  console.log("开始");
  const data = await fetchDevices();  // 等待(不阻塞主线程)
  console.log("拿到:", data);
}

规则

  • await 只能用在 async 函数内

  • await 后面跟 Promise,等完成后拿到 resolve 值

  • 错误用 try/catch 捕获


二、用 async/await 改写链式调用

// Promise 链
login("admin")
  .then(({ token }) => getDevices(token))
  .then(devices => getDetail(devices[0].id))
  .catch(err => console.error(err));

// async/await(像同步一样清晰!)
async function main() {
  try {
    const { token } = await login("admin");
    const devices = await getDevices(token);
    const detail = await getDetail(devices[0].id);
    console.log(detail);
  } catch (err) {
    console.error(err.message);  // try/catch 统一捕获
  }
}

三、Promise.all 四剑客

方法

成功条件

场景

all

全部成功

大屏多接口聚合(缺一不可)

race

第一个完成

超时控制

allSettled

永不失败

部分成功也有价值

any

第一个成功

多镜像源取最快

// all:并发请求,解构接收
const [devices, alerts, output] = await Promise.all([
  fetch("/api/devices"),
  fetch("/api/alerts"),
  fetch("/api/output")
]);

// race:超时控制
Promise.race([
  fetch("/api/data"),
  new Promise((_, reject) =>
    setTimeout(() => reject(new Error("超时")), 3000))
]);

// allSettled:部分失败不崩溃
const results = await Promise.allSettled([p1, p2, p3]);
results.forEach(r => {
  if (r.status === "fulfilled") console.log(r.value);
  else console.log("失败:", r.reason.message);
});

四、顺序 vs 并发

// ❌ 顺序:无依赖却 await 串行(总耗时 = 3 秒)
for (const id of [1, 2, 3]) {
  results.push(await fetch(`/api/device/${id}`));
}

// ✅ 并发:无依赖用 Promise.all(总耗时 = 1 秒)
const results = await Promise.all(
  [1, 2, 3].map(id => fetch(`/api/device/${id}`))
);

原则:有依赖 → 顺序 await;无依赖 → Promise.all 并发。


五、fetch 真实请求

// GET
async function loadDevices() {
  try {
    const response = await fetch("https://api.example.com/devices");
    // ⭐ fetch 不会因 404/500 reject!必须手动检查 ok
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const data = await response.json();  // 解析也是异步
    return data;
  } catch (err) {
    console.error("请求失败:", err.message);
    return [];  // 降级
  }
}

// POST
const response = await fetch(url, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(device)
});

fetch 三要点

  1. response.ok:200-299 才是 true

  2. response.json():解析响应体,要 await

  3. 只有网络错误才 reject,HTTP 错误码不会


六、综合实战:大屏数据加载

完整流程:loading → 并发请求 → 渲染 → 错误处理 → 重试。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>大屏数据加载实战</title>
  <style>
    body { font-family: sans-serif; background: #0a1628; color: #e0e0e0; }
    .loading { text-align: center; padding: 60px; color: #00ff88; }
    .spinner {
      display: inline-block; width: 40px; height: 40px;
      border: 4px solid rgba(0,255,136,0.2);
      border-top-color: #00ff88; border-radius: 50%;
      animation: spin 1s linear infinite;
    }
    @keyframes spin { to { transform: rotate(360deg); } }
    .panel { background: #112240; margin: 10px; padding: 16px; border-radius: 8px; }
    .stat { display: inline-block; margin: 0 20px; }
    .stat strong { color: #00ff88; font-size: 24px; }
    .error { color: #ff5252; text-align: center; padding: 60px; }
    .error button { padding: 8px 24px; cursor: pointer; }
  </style>
</head>
<body>
  <div id="app">
    <div class="loading"><span class="spinner"></span><p>数据加载中...</p></div>
  </div>

  <script>
    // ===== 模拟接口层(真实项目换成 fetch)=====
    const mockDevices = () => new Promise(resolve =>
      setTimeout(() => resolve([
        { id: "CNC-001", temp: 65, status: "运行中" },
        { id: "AGV-002", temp: 42, status: "运行中" },
        { id: "ARM-003", temp: 38, status: "待机" }
      ]), 800));

    const mockAlerts = () => new Promise((resolve, reject) =>
      setTimeout(() => {
        Math.random() > 0.2
          ? resolve([{ device: "CNC-001", level: "warn", msg: "温度偏高" }])
          : reject(new Error("告警服务不可用"));
      }, 600));

    const mockOutput = () => new Promise(resolve =>
      setTimeout(() => resolve({ today: 1250, week: 8600 }), 1000));

    // ===== 渲染层(纯函数,只拼 HTML)=====
    function renderDashboard({ devices, alerts, output }) {
      const onlineCount = devices.filter(d => d.status === "运行中").length;
      return `
        <div class="panel">
          <span class="stat">设备总数 <strong>${devices.length}</strong></span>
          <span class="stat">在线 <strong>${onlineCount}</strong></span>
          <span class="stat">今日产量 <strong>${output.today}</strong></span>
          <span class="stat">告警 <strong>${alerts.length}</strong></span>
        </div>
        <div class="panel">
          ${devices.map(d => `<p>${d.id}:${d.temp}°C(${d.status})</p>`).join("")}
        </div>
      `;
    }

    // ===== 主流程 =====
    async function initDashboard() {
      const app = document.getElementById("app");
      try {
        // 三个接口无依赖 → 并发(总耗时 ≈ 最慢的 1 秒)
        const [devices, alerts, output] = await Promise.all([
          mockDevices(),
          // 告警失败不阻塞大屏:降级为空数组
          mockAlerts().catch(err => {
            console.warn("降级:", err.message);
            return [];
          }),
          mockOutput()
        ]);
        app.innerHTML = renderDashboard({ devices, alerts, output });
      } catch (err) {
        // 核心接口失败 → 错误页 + 重试
        app.innerHTML = `
          <div class="error">
            <p>加载失败:${err.message}</p>
            <button onclick="initDashboard()">重试</button>
          </div>
        `;
      }
    }

    initDashboard();
  </script>
</body>
</html>

工程要点

  1. loading 状态:请求前显示,完成后替换

  2. 并发请求:Promise.all,耗时 = 最慢接口

  3. 降级策略:非核心接口失败给默认值,不阻塞整体

  4. 错误页 + 重试:核心失败给用户出路

  5. 渲染分离:渲染是纯函数,方便测试


七、总结

语法

用途

async function

返回 Promise 的函数

await promise

等待并取值

try/catch

捕获 await 的错误

Promise.all

并发(全部成功)

Promise.race

超时控制

Promise.allSettled

部分失败容错

response.ok

fetch 必查状态码

🎯 至此异步编程闭环:Promise → 并发 → async/await → 真实请求。下周学 Class 与模块化,把代码组织成专业工程!

评论