Node.js async/await 语法

核心知识点

async/await 概念

async/await 是 ES2017 引入的语法特性,它是基于 Promise 的语法糖,让异步代码的写法更接近同步代码,提高了代码的可读性和可维护性。

async 函数

async 关键字用于声明一个异步函数:

async function asyncFunction() {
  // 函数体
  return 'Hello, async!';
}

await 表达式

await 关键字用于等待一个 Promise 完成,只能在 async 函数内部使用:

async function asyncFunction() {
  const result = await promise;
  return result;
}

错误处理

async/await 中,可以使用 try/catch 语法来处理错误:

async function asyncFunction() {
  try {
    const result = await promise;
    return result;
  } catch (error) {
    console.error('发生错误:', error);
    // 处理错误
  }
}

async/await 与 Promise 的关系

async/await 是基于 Promise 的,每个 async 函数都会返回一个 Promise,await 表达式会等待这个 Promise 完成。

实用案例

案例一:基本 async/await 使用

const fs = require('fs').promises; // Node.js 10+ 提供的 Promise API

// 使用 async/await 处理文件操作
async function processFiles() {
  try {
    // 读取文件
    const data = await fs.readFile('example.txt', 'utf8');
    console.log('读取的文件内容:', data);
    
    // 写入文件
    await fs.writeFile('output.txt', data.toUpperCase());
    console.log('文件写入成功');
    
    // 读取新文件
    const newData = await fs.readFile('output.txt', 'utf8');
    console.log('写入后读取的内容:', newData);
    
    return '文件处理完成';
  } catch (error) {
    console.error('处理文件时发生错误:', error);
    throw error; // 可以选择重新抛出错误
  }
}

// 调用异步函数
processFiles()
  .then(result => {
    console.log('最终结果:', result);
  })
  .catch(error => {
    console.error('捕获到错误:', error);
  });

// 或者在另一个 async 函数中调用
async function main() {
  try {
    const result = await processFiles();
    console.log('从 main 函数中获取结果:', result);
  } catch (error) {
    console.error('main 函数中的错误:', error);
  }
}

main();

案例二:并行执行异步操作

const fs = require('fs').promises;

async function parallelFileOperations() {
  try {
    // 并行读取多个文件
    const [data1, data2, data3] = await Promise.all([
      fs.readFile('file1.txt', 'utf8'),
      fs.readFile('file2.txt', 'utf8'),
      fs.readFile('file3.txt', 'utf8')
    ]);
    
    console.log('文件1内容:', data1);
    console.log('文件2内容:', data2);
    console.log('文件3内容:', data3);
    
    // 合并内容
    const combinedContent = data1 + '\n' + data2 + '\n' + data3;
    
    // 写入合并后的内容
    await fs.writeFile('combined.txt', combinedContent);
    console.log('文件合并完成');
    
    return combinedContent;
  } catch (error) {
    console.error('并行操作时发生错误:', error);
    throw error;
  }
}

parallelFileOperations()
  .then(result => {
    console.log('合并后的内容长度:', result.length);
  })
  .catch(error => {
    console.error('捕获到错误:', error);
  });

案例三:异步函数链式调用

const http = require('http');

// 封装 HTTP 请求为 Promise
function httpGetPromise(url) {
  return new Promise((resolve, reject) => {
    http.get(url, (response) => {
      let data = '';
      
      response.on('data', (chunk) => {
        data += chunk;
      });
      
      response.on('end', () => {
        resolve(data);
      });
      
      response.on('error', (error) => {
        reject(error);
      });
    }).on('error', (error) => {
      reject(error);
    });
  });
}

// 使用 async/await 链式调用
async function fetchAndProcessData() {
  try {
    // 获取数据
    const data = await httpGetPromise('http://example.com');
    console.log('获取到数据,长度:', data.length);
    
    // 处理数据
    const processedData = await processData(data);
    console.log('处理后的数据长度:', processedData.length);
    
    // 进一步处理
    const finalResult = await finalProcess(processedData);
    console.log('最终结果:', finalResult);
    
    return finalResult;
  } catch (error) {
    console.error('处理数据时发生错误:', error);
    throw error;
  }
}

// 模拟异步数据处理函数
async function processData(data) {
  // 模拟异步操作
  await new Promise(resolve => setTimeout(resolve, 1000));
  return data.substring(0, 1000);
}

// 模拟最终处理函数
async function finalProcess(data) {
  // 模拟异步操作
  await new Promise(resolve => setTimeout(resolve, 500));
  return `处理完成,数据前50个字符: ${data.substring(0, 50)}...`;
}

// 调用
fetchAndProcessData()
  .then(result => {
    console.log('操作完成:', result);
  })
  .catch(error => {
    console.error('捕获到错误:', error);
  });

案例四:使用 async/await 重构 Promise 代码

const fs = require('fs').promises;

// Promise 版本
function processFilesPromise() {
  return fs.readFile('file1.txt', 'utf8')
    .then(data1 => {
      return Promise.all([Promise.resolve(data1), fs.readFile('file2.txt', 'utf8')]);
    })
    .then(([data1, data2]) => {
      const combined = data1 + data2;
      return fs.writeFile('combined.txt', combined);
    })
    .then(() => {
      return '文件处理完成';
    })
    .catch(err => {
      console.error('处理文件时发生错误:', err);
      throw err;
    });
}

// async/await 版本
async function processFilesAsyncAwait() {
  try {
    // 并行读取两个文件
    const [data1, data2] = await Promise.all([
      fs.readFile('file1.txt', 'utf8'),
      fs.readFile('file2.txt', 'utf8')
    ]);
    
    // 合并内容
    const combined = data1 + data2;
    
    // 写入文件
    await fs.writeFile('combined.txt', combined);
    
    return '文件处理完成';
  } catch (error) {
    console.error('处理文件时发生错误:', error);
    throw error;
  }
}

// 调用两种版本
console.log('使用 Promise 处理文件:');
processFilesPromise()
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('捕获到错误:', error);
  });

console.log('\n使用 async/await 处理文件:');
processFilesAsyncAwait()
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error('捕获到错误:', error);
  });

学习目标

  1. 理解 async/await 概念:掌握 async/await 的基本原理和工作机制
  2. 掌握 async 函数:学会声明和使用 async 函数
  3. 熟练使用 await:能够在 async 函数中使用 await 等待 Promise 完成
  4. 掌握错误处理:学会在 async/await 中使用 try/catch 处理错误
  5. 并行执行操作:能够使用 Promise.all() 并行执行多个异步操作
  6. 重构代码:能够将 Promise 风格的代码重构为 async/await 风格

代码优化建议

1. 合理使用并行操作

不好的做法

async function sequentialOperations() {
  const result1 = await asyncOperation1();
  const result2 = await asyncOperation2(); // 必须等待前一个操作完成
  const result3 = await asyncOperation3(); // 必须等待前一个操作完成
  return [result1, result2, result3];
}

好的做法

async function parallelOperations() {
  const [result1, result2, result3] = await Promise.all([
    asyncOperation1(),
    asyncOperation2(),
    asyncOperation3()
  ]);
  return [result1, result2, result3];
}

2. 正确处理错误

不好的做法

async function errorProneFunction() {
  const result = await somePromise(); // 没有错误处理
  return result;
}

好的做法

async function safeFunction() {
  try {
    const result = await somePromise();
    return result;
  } catch (error) {
    console.error('发生错误:', error);
    // 可以选择:
    // 1. 返回默认值
    // 2. 重新抛出错误
    // 3. 处理错误并继续
    return null; // 返回默认值
  }
}

3. 避免在非异步函数中使用 await

不好的做法

function regularFunction() {
  const result = await somePromise(); // 错误:不能在非 async 函数中使用 await
  return result;
}

好的做法

async function asyncFunction() {
  const result = await somePromise();
  return result;
}

// 或者在立即执行函数表达式中使用
(function() {
  async function wrapper() {
    const result = await somePromise();
    console.log(result);
  }
  wrapper();
})();

// 或者使用 top-level await (Node.js 14.8+)
// await somePromise();

4. 注意 async/await 的性能

不好的做法

async function processArray(array) {
  const results = [];
  for (const item of array) {
    const result = await processItem(item); // 串行处理
    results.push(result);
  }
  return results;
}

好的做法

async function processArray(array) {
  const promises = array.map(item => processItem(item)); // 并行处理
  const results = await Promise.all(promises);
  return results;
}

常见问题与解决方案

问题1:await 只能在 async 函数中使用

原因

  • await 关键字只能在 async 函数内部使用

解决方案

  • 将函数声明为 async 函数
  • 使用立即执行的 async 函数表达式
  • 在支持的环境中使用 top-level await

问题2:未处理的 Promise 拒绝

原因

  • async 函数返回的 Promise 被拒绝但没有被处理

解决方案

  • 始终处理 async 函数返回的 Promise 的拒绝
  • 使用 try/catch 处理 async 函数内部的错误

问题3:async/await 导致的性能问题

原因

  • 串行执行多个独立的异步操作

解决方案

  • 使用 Promise.all() 并行执行多个独立的异步操作
  • 合理使用 Promise.race() 设置超时

问题4:async/await 中的错误捕获

原因

  • 嵌套的 async 函数中的错误没有被正确捕获

解决方案

  • 确保每个 async 函数都有错误处理
  • 使用 try/catch 捕获所有可能的错误

总结

async/await 是 Node.js 中处理异步操作的现代方式,通过本教程的学习,你应该能够:

  1. 理解 async/await 的基本概念和工作原理
  2. 掌握 async 函数的声明和使用
  3. 熟练使用 await 表达式等待 Promise 完成
  4. 正确处理 async/await 中的错误
  5. 合理使用并行操作提高性能
  6. 将 Promise 风格的代码重构为 async/await 风格

async/await 让异步代码的写法更接近同步代码,大大提高了代码的可读性和可维护性,是现代 Node.js 开发中的推荐做法。在实际项目中,你应该尽可能使用 async/await 来处理异步操作,同时注意合理使用并行操作来提高性能。

« 上一篇 Node.js Promise 对象 下一篇 » Node.js HTTP 服务器基础