Administrator
Administrator
发布于 2025-08-06 / 1 阅读

JavaScript 异步编程指南:async/await 的使用场景与规范

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

JavaScript 异步编程指南:async/await 的使用场景与规范

一、async/await 是什么?为什么它是异步编程的救星?

1. 核心概念

asyncawait是 ES2017 推出的异步编程语法糖,本质是 Promise 的语法简化版:

  • async修饰的函数会隐式返回 Promise,相当于自动给返回值套上Promise.resolve()
  • await只能在async函数内使用,能让代码 "暂停" 等待 Promise 结果,使异步代码拥有同步写法的直观性

2. 对比传统异步方案

回调函数

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

Promise 链式调用

fetch('api/data')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

async/await

async function getData() {
  try {
    const res = await fetch('api/data');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

优势:代码结构更接近同步逻辑,避免回调地狱和 Promise 多层嵌套,错误处理更统一。

二、核心用法详解

1. async 函数基础语法

// 定义async函数,返回Promise
async function getUser() {
  return { id: 1, name: "张三" };
}

// 等价于
function getUser() {
  return Promise.resolve({ id: 1, name: "张三" });
}

// 调用方式
getUser().then(user => {
  console.log(user.name); // 输出:张三
});

2. await 关键字的正确姿势

async function showDelayMessage() {
  // 封装延迟Promise
  const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
  
  console.log("开始执行");
  await delay(2000); // 代码在此暂停2秒
  console.log("2秒后执行");
  
  const message = await new Promise(resolve => {
    resolve("延迟消息");
  });
  console.log(message); // 输出:延迟消息
}

showDelayMessage();

3. 错误处理最佳实践

async function fetchData(url) {
  try {
    // 等待请求响应
    const response = await fetch(url);
    
    // 检查响应状态
    if (!response.ok) {
      throw new Error(`请求失败: ${response.status}`);
    }
    
    // 等待数据解析
    const data = await response.json();
    return data;
  } catch (error) {
    console.error("数据获取失败:", error);
    throw error; // 可选:向上抛出错误
  }
}

三、六大典型使用场景与实战案例

1. 场景一:网络请求(最常用场景)

需求:获取用户信息后再获取该用户的帖子

async function getUserAndPosts(userId) {
  try {
    // 顺序执行异步操作
    const userResponse = await fetch(`/api/users/${userId}`);
    const user = await userResponse.json();
    
    const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
    const posts = await postsResponse.json();
    
    return { user, posts };
  } catch (error) {
    console.error("请求失败:", error);
  }
}

2. 场景二:并行请求优化

需求:同时获取用户、帖子、评论数据

async function fetchAllData() {
  try {
    // 并行发起多个请求
    const userPromise = fetch("/api/user/1").then(res => res.json());
    const postsPromise = fetch("/api/posts").then(res => res.json());
    const commentsPromise = fetch("/api/comments").then(res => res.json());
    
    // 等待所有请求完成
    const [user, posts, comments] = await Promise.all([
      userPromise,
      postsPromise,
      commentsPromise
    ]);
    
    return { user, posts, comments };
  } catch (error) {
    console.error("任一请求失败:", error);
  }
}

3. 场景三:定时器延迟控制

需求:实现一个可复用的延迟函数

// 封装延迟Promise
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

async function taskWithDelay() {
  console.log("任务开始");
  
  // 等待3秒
  await delay(3000);
  console.log("3秒后执行");
  
  // 再等待1秒
  await delay(1000);
  console.log("4秒后执行");
}

4. 场景四:文件系统操作(Node.js)

需求:读取配置文件并解析

const fs = require('fs').promises; // Node.js内置Promise化API

async function loadConfig() {
  try {
    // 读取文件内容
    const content = await fs.readFile('./config.json', 'utf-8');
    // 解析JSON
    const config = JSON.parse(content);
    return config;
  } catch (error) {
    console.error("配置加载失败:", error);
    throw new Error("配置文件异常,请检查路径");
  }
}

5. 场景五:表单提交与验证

需求:前端表单提交前验证并发送请求

async function handleFormSubmit(formData) {
  // 前端验证
  if (!formData.username || !formData.password) {
    throw new Error("用户名和密码不能为空");
  }
  
  try {
    // 发送提交请求
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify(formData),
      headers: { 'Content-Type': 'application/json' }
    });
    
    const result = await response.json();
    
    if (result.success) {
      return "登录成功";
    } else {
      throw new Error(result.message || "登录失败");
    }
  } catch (error) {
    console.error("提交失败:", error);
    throw error;
  }
}

6. 场景六:处理流数据(Node.js)

需求:读取大文件并逐行处理

const fs = require('fs');
const readline = require('readline');

async function processLargeFile(filePath) {
  try {
    const fileStream = fs.createReadStream(filePath);
    const rl = readline.createInterface({
      input: fileStream,
      crlfDelay: Infinity
    });
    
    let lineCount = 0;
    // 逐行处理
    for await (const line of rl) {
      lineCount++;
      // 处理每行数据
      console.log(`行${lineCount}: ${line}`);
    }
    
    console.log(`处理完成,共${lineCount}行`);
  } catch (error) {
    console.error("文件处理失败:", error);
  }
}

四、企业级代码规范与最佳实践

1. 命名与定义规范

  • 函数命名:async 函数建议添加Async后缀(可选),如fetchUserAsync()

  • 定义方式:优先使用普通函数定义,避免箭头函数(可读性更好)

    // 推荐
    async function loadData() {}
    
    // 不推荐
    const loadData = async () => {}
    
  • 变量命名:await 后的变量名建议体现异步操作含义,如await fetchResponse

2. 错误处理规范

  • 必用 try…catch:所有 await 操作必须包裹在 try 块中,catch 统一处理错误

    async function main() {
      try {
        const data = await fetchData();
        await processData(data);
      } catch (error) {
        console.error("全局错误捕获:", error);
        // 记录错误日志(如上报到监控系统)
        sendErrorToMonitor(error);
      }
    }
    
  • 错误分类处理:根据错误类型做不同处理(示例)

    async function handleError() {
      try {
        // ...
      } catch (error) {
        if (error instanceof NetworkError) {
          console.log("网络错误,重试中...");
          // 重试逻辑
        } else if (error instanceof AuthError) {
          console.log("认证失败,跳转登录页");
          // 跳转逻辑
        } else {
          console.error("未知错误:", error);
        }
      }
    }
    

3. 性能优化规范

  • 并行任务用 Promise.all:避免顺序等待浪费时间

    // 差:顺序执行(总耗时=3s+2s=5s)
    async function bad() {
      await delay(3000);
      await delay(2000);
    }
    
    // 好:并行执行(总耗时=3s)
    async function good() {
      await Promise.all([delay(3000), delay(2000)]);
    }
    
  • 限制并发数量:处理大量并行请求时控制并发数(示例)

    async function fetchWithConcurrency(urls, maxConcurrent) {
      const results = [];
      const fetchQueue = urls.map(url => {
        return async () => {
          try {
            const res = await fetch(url);
            results.push(await res.json());
          } catch (err) {
            results.push(err);
          }
        };
      });
    
      // 分批执行
      for (let i = 0; i < fetchQueue.length; i += maxConcurrent) {
        const batch = fetchQueue.slice(i, i + maxConcurrent);
        await Promise.all(batch.map(fetchFn => fetchFn()));
      }
    
      return results;
    }
    

4. 代码风格规范

  • 一行一 await:每个 await 单独一行,提高可读性

    // 推荐
    const response = await fetch(url);
    const data = await response.json();
    
    // 不推荐
    const data = await await fetch(url).then(res => res.json());
    
  • 合理使用 return:及时 return 避免不必要的等待

    async function getData() {
      if (cacheAvailable) {
        return getFromCache(); // 直接返回缓存数据,无需等待
      }
    
      const data = await fetchFromServer();
      return data;
    }
    

5. 特殊场景处理

  • 处理超时:为 await 操作添加超时控制

    // 封装超时Promise
    function timeout(ms) {
      return new Promise((_, reject) => {
        setTimeout(() => reject(new Error("操作超时")), ms);
      });
    }
    
    async function fetchWithTimeout(url, timeoutMs) {
      try {
        return await Promise.race([
          fetch(url),
          timeout(timeoutMs)
        ]);
      } catch (error) {
        console.error("请求超时:", error);
        throw error;
      }
    }
    
  • 处理取消操作:使用 AbortController 取消异步请求

    async function fetchWithAbort(url) {
      const controller = new AbortController();
      const signal = controller.signal;
    
      // 5秒后取消请求
      setTimeout(() => {
        controller.abort();
        console.log("请求已取消");
      }, 5000);
    
      try {
        const response = await fetch(url, { signal });
        return await response.json();
      } catch (error) {
        if (error.name === "AbortError") {
          console.log("请求被取消");
        } else {
          console.error("请求失败:", error);
        }
      }
    }
    

五、浏览器兼容性与解决方案

1. 主流浏览器支持情况

浏览器最低支持版本支持时间
Chrome55+2016 年 12 月
Firefox52+2017 年 3 月
Safari11+2017 年 9 月
Edge15+2017 年 4 月
Opera42+2016 年 12 月

2. 兼容性解决方案

方案一:使用 babel 转译
  1. 安装依赖:

    npm install @babel/core @babel/preset-env @babel/cli -D
    
  2. 创建.babelrc配置文件:

    {
      "presets": [
        ["@babel/preset-env", {
          "targets": {
            "browsers": ["last 2 versions", "ie >= 11"]
          }
        }]
      ]
    }
    
  3. 转译命令:

    npx babel src -d dist
    
方案二:引入 polyfill
  1. 安装regenerator-runtime

    npm install regenerator-runtime
    
  2. 在代码中引入:

    import "regenerator-runtime/runtime";
    

六、进阶技巧与面试高频问题

1. 如何处理 Promise.all 中的部分失败?

async function fetchWithPartialFail(urls) {
  const results = [];
  
  for (const url of urls) {
    try {
      const res = await fetch(url);
      results.push(await res.json());
    } catch (error) {
      results.push({ error: true, message: error.message });
      console.log(`请求${url}失败`, error);
    }
  }
  
  return results;
}

2. await 能在普通函数中使用吗?为什么?

不能。因为await必须在async函数内使用,本质上async函数会被编译为生成器函数(Generator),而await是生成器中yield的语法糖,普通函数无法使用该特性。

3. async/await 和 Promise 的本质区别?

  • async/await是语法糖,底层仍基于 Promise 实现
  • async/await让异步代码拥有同步的写法,更符合人类思维习惯
  • async/await通过try...catch统一处理错误,比 Promise 的.catch()更直观

七、总结:async/await 使用三原则

  1. 能用 async/await 就不用传统 Promise:除非需要处理极其复杂的异步流程控制
  2. 每个 await 必配 try…catch:避免未捕获的异步错误导致程序崩溃
  3. 合理选择并行 / 顺序执行:IO 密集型任务用 Promise.all 并行处理,计算密集型任务考虑 Web Worker

掌握 async/await 不仅能写出更优雅的异步代码,还能提升效率。