阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > async/await语法糖

async/await语法糖

作者:陈川 阅读数:51152人阅读 分类: Node.js

async/await的本质

async/await是Generator函数的语法糖,底层基于Promise实现。它让异步代码看起来像同步代码,但实际执行过程仍然是异步的。async函数返回一个Promise对象,await命令后面通常是一个Promise对象。

async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

async函数的基本用法

在函数声明前加上async关键字,就变成了async函数。async函数内部可以使用await表达式,await会暂停async函数的执行,等待Promise解决后再继续执行。

async function getUser(id) {
  try {
    const response = await fetch(`/users/${id}`);
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return await response.json();
  } catch (error) {
    console.error('Fetch error:', error);
  }
}

await表达式的行为

await表达式会暂停async函数的执行,等待Promise解决。如果等待的不是Promise对象,则直接返回该值本身。await只能在async函数内部使用。

async function process() {
  const value1 = await 42; // 非Promise值直接返回
  const value2 = await Promise.resolve('hello');
  console.log(value1, value2); // 42 "hello"
}

错误处理机制

async函数内部可以使用try/catch捕获错误,也可以使用Promise的catch方法处理错误。未捕获的错误会导致返回的Promise被reject。

// 使用try/catch
async function fetchWithRetry(url, retries = 3) {
  try {
    const response = await fetch(url);
    return await response.json();
  } catch (err) {
    if (retries <= 0) throw err;
    return fetchWithRetry(url, retries - 1);
  }
}

// 使用catch方法
fetchWithRetry('https://api.example.com/data')
  .then(data => console.log(data))
  .catch(err => console.error('Failed after retries:', err));

并行执行多个异步操作

当需要并行执行多个不依赖的异步操作时,可以使用Promise.all配合await,而不是顺序await每个操作。

async function fetchMultiple() {
  const [user, posts] = await Promise.all([
    fetch('/user/1').then(r => r.json()),
    fetch('/posts?userId=1').then(r => r.json())
  ]);
  return { user, posts };
}

async函数的执行顺序

理解async函数的执行顺序很重要,await会暂停函数执行,但不会阻塞事件循环。

console.log('Start');

async function asyncFunc() {
  console.log('Async function start');
  await Promise.resolve();
  console.log('Async function after await');
}

asyncFunc();

console.log('End');

// 输出顺序:
// Start
// Async function start
// End
// Async function after await

在循环中使用await

在循环中使用await需要注意执行顺序,有时需要并行执行而不是顺序执行。

// 顺序执行
async function processArray(array) {
  for (const item of array) {
    await processItem(item); // 每个item顺序处理
  }
}

// 并行执行
async function processArrayParallel(array) {
  await Promise.all(array.map(item => processItem(item)));
}

async函数与Promise的互操作性

async函数返回Promise,因此可以与其他Promise API无缝协作。

async function getUser(id) {
  return { id, name: `User ${id}` };
}

// 可以像普通Promise一样使用
getUser(1)
  .then(user => console.log(user))
  .catch(err => console.error(err));

// 也可以在另一个async函数中使用
async function printUser(id) {
  const user = await getUser(id);
  console.log(user);
}

常见使用场景

async/await特别适合处理需要顺序执行的异步操作,如API调用链、文件处理等。

async function processOrder(orderId) {
  const order = await fetchOrder(orderId);
  const user = await fetchUser(order.userId);
  const address = await fetchAddress(user.addressId);
  await sendConfirmationEmail(user.email, order, address);
  return { order, user, address };
}

性能考虑

虽然async/await使代码更易读,但不当使用可能影响性能。避免不必要的await,合理并行化操作。

// 低效写法
async function inefficient() {
  const a = await fetchA();
  const b = await fetchB(); // 等待fetchA完成才开始
  return a + b;
}

// 高效写法
async function efficient() {
  const [a, b] = await Promise.all([fetchA(), fetchB()]);
  return a + b;
}

在类方法中使用

async函数也可以作为类的方法使用,保持相同的语法和行为。

class ApiClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }

  async get(resource) {
    const response = await fetch(`${this.baseUrl}/${resource}`);
    return response.json();
  }
}

const client = new ApiClient('https://api.example.com');
client.get('users/1').then(user => console.log(user));

与生成器函数的比较

async/await可以看作是生成器函数的语法糖,但更专注于异步流程控制。

// 使用生成器函数
function* generatorFetch() {
  const response = yield fetch('/data');
  const data = yield response.json();
  return data;
}

// 使用async/await
async function asyncFetch() {
  const response = await fetch('/data');
  const data = await response.json();
  return data;
}

在Node.js中的实际应用

在Node.js中,async/await常用于文件操作、数据库查询等I/O密集型任务。

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

async function readFiles() {
  try {
    const file1 = await fs.readFile('file1.txt', 'utf8');
    const file2 = await fs.readFile('file2.txt', 'utf8');
    return { file1, file2 };
  } catch (err) {
    console.error('Error reading files:', err);
    throw err;
  }
}

与事件发射器的结合使用

可以将事件发射器包装成Promise,以便在async函数中使用。

const { EventEmitter } = require('events');

function eventToPromise(emitter, eventName) {
  return new Promise((resolve, reject) => {
    emitter.once(eventName, resolve);
    emitter.once('error', reject);
  });
}

async function waitForEvent() {
  const emitter = new EventEmitter();
  setTimeout(() => emitter.emit('data', 'Hello'), 1000);
  const data = await eventToPromise(emitter, 'data');
  console.log(data); // 'Hello'
}

在Express路由中的使用

在Express路由处理函数中使用async/await可以简化错误处理。

const express = require('express');
const app = express();

app.get('/user/:id', async (req, res, next) => {
  try {
    const user = await getUserById(req.params.id);
    if (!user) return res.status(404).send('User not found');
    res.json(user);
  } catch (err) {
    next(err); // 错误会传递给错误处理中间件
  }
});

async function getUserById(id) {
  // 模拟数据库查询
  return new Promise(resolve => {
    setTimeout(() => resolve({ id, name: `User ${id}` }), 100);
  });
}

与流处理的结合

虽然流处理通常基于事件,但可以创建辅助函数使其与async/await兼容。

const { pipeline } = require('stream/promises');
const fs = require('fs');

async function processFile(inputFile, outputFile) {
  try {
    await pipeline(
      fs.createReadStream(inputFile),
      // 这里可以添加转换流
      fs.createWriteStream(outputFile)
    );
    console.log('Pipeline succeeded');
  } catch (err) {
    console.error('Pipeline failed:', err);
  }
}

在测试中的使用

async/await可以简化异步测试代码,使测试用例更清晰。

const assert = require('assert');
const { test } = require('node:test');

test('async test example', async () => {
  const result = await someAsyncFunction();
  assert.strictEqual(result, expectedValue);
});

async function someAsyncFunction() {
  return new Promise(resolve => setTimeout(() => resolve(42), 10));
}

调试async函数

调试async函数与调试同步代码类似,但需要注意调用栈和断点位置。

async function debugExample() {
  console.log('Start debugging');
  const result = await fetchData(); // 可以在这里设置断点
  console.log('Data:', result);
  const processed = process(result); // 另一个断点位置
  return processed;
}

浏览器兼容性和转译

现代浏览器都支持async/await,但对于旧环境可能需要Babel转译。

// Babel会将其转换为兼容代码
async function oldBrowserSupport() {
  const data = await fetchData();
  console.log(data);
}

与Web Worker的交互

在Web Worker中使用async/await可以简化消息处理。

// worker.js
self.onmessage = async (event) => {
  try {
    const result = await heavyComputation(event.data);
    self.postMessage({ status: 'success', result });
  } catch (err) {
    self.postMessage({ status: 'error', error: err.message });
  }
};

async function heavyComputation(data) {
  // 模拟耗时计算
  return new Promise(resolve => {
    setTimeout(() => resolve(data * 2), 1000);
  });
}

在定时器中的使用

可以将setTimeout等定时器函数Promise化,以便在async函数中使用。

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

async function timedOperation() {
  console.log('Start');
  await delay(1000);
  console.log('After 1 second');
  await delay(500);
  console.log('After another 0.5 second');
}

与第三方库的集成

许多现代Node.js库都支持Promise,可以无缝与async/await集成。

const axios = require('axios');

async function fetchFromMultipleAPIs() {
  const [github, reddit] = await Promise.all([
    axios.get('https://api.github.com/users/octocat'),
    axios.get('https://www.reddit.com/r/node.json')
  ]);
  return { github: github.data, reddit: reddit.data };
}

递归async函数

async函数可以递归调用,但要注意堆栈和性能。

async function recursiveFetch(url, depth = 0) {
  if (depth > 3) return null;
  const response = await fetch(url);
  const data = await response.json();
  if (data.next) {
    return recursiveFetch(data.next, depth + 1);
  }
  return data;
}

在CLI工具中的应用

Node.js CLI工具中,async/await可以简化用户输入处理等异步操作。

const readline = require('readline').createInterface({
  input: process.stdin,
  output: process.stdout
});

function questionAsync(prompt) {
  return new Promise(resolve => {
    readline.question(prompt, resolve);
  });
}

async function interactiveCLI() {
  const name = await questionAsync('What is your name? ');
  const age = await questionAsync('How old are you? ');
  console.log(`Hello ${name}, you are ${age} years old`);
  readline.close();
}

内存管理注意事项

长时间运行的async函数可能持有不必要的引用,需要注意内存泄漏问题。

async function processLargeData() {
  const bigData = await getLargeData(); // 大数据
  const result = computeResult(bigData);
  // 明确清除不再需要的大数据引用
  bigData = null;
  return result;
}

与WebSocket的交互

WebSocket通信可以封装为Promise,以便在async函数中使用。

function websocketPromise(ws, message) {
  return new Promise((resolve, reject) => {
    ws.send(message);
    ws.once('message', resolve);
    ws.once('error', reject);
    ws.once('close', () => reject(new Error('WebSocket closed')));
  });
}

async function wsCommunication(ws) {
  try {
    const response = await websocketPromise(ws, 'ping');
    console.log('Received:', response);
  } catch (err) {
    console.error('WebSocket error:', err);
  }
}

在微服务架构中的应用

在微服务间调用时,async/await可以简化跨服务通信代码。

async function placeOrder(userId, items) {
  const user = await userService.getUser(userId);
  const inventory = await inventoryService.checkItems(items);
  const payment = await paymentService.processPayment(user, items);
  const order = await orderService.createOrder(user, items, payment);
  await notificationService.sendOrderConfirmation(user, order);
  return order;
}

与GraphQL解析器的结合

GraphQL解析器非常适合使用async/await,因为经常需要从多个数据源获取数据。

const resolvers = {
  Query: {
    user: async (_, { id }) => {
      const user = await db.users.findById(id);
      const posts = await db.posts.findByUserId(id);
      return { ...user, posts };
    }
  },
  Mutation: {
    createPost: async (_, { input }, context) => {
      if (!context.user) throw new Error('Unauthorized');
      return db.posts.create({ ...input, authorId: context.user.id });
    }
  }
};

在Serverless函数中的使用

Serverless函数通常需要处理各种异步操作,async/await是理想选择。

// AWS Lambda示例
exports.handler = async (event) => {
  try {
    const data = await processEvent(event);
    return {
      statusCode: 200,
      body: JSON.stringify(data)
    };
  } catch (err) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: err.message })
    };
  }
};

async function processEvent(event) {
  // 处理事件数据
  return { processed: true, timestamp: Date.now() };
}

与TypeScript的类型系统结合

TypeScript为async函数提供了良好的类型支持,可以精确描述返回值类型。

interface User {
  id: number;
  name: string;
}

async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`/users/${id}`);
  return response.json();
}

async function printUserName(id: number): Promise<void> {
  const user = await fetchUser(id);
  console.log(user.name);
}

在中间件管道中的应用

中间件管道可以设计为async函数链,便于处理请求和响应。

async function middlewarePipeline(context, middlewares) {
  for (const middleware of middlewares) {
    await middleware(context);
    if (context.finished) break;
  }
  return context;
}

// 使用示例
const context = { request: {}, response: {} };
await middlewarePipeline(context, [
  async (ctx) => { ctx.user = await getUser(ctx.request); },
  async (ctx) => { if (!ctx.user) throw new Error('Unauthorized'); },
  async (ctx) => { ctx.response.data = await getData(ctx.user); }
]);

与数据库事务的结合

数据库事务处理是async/await的典型应用场景,需要顺序执行多个操作。

async function transferFunds(senderId, receiverId, amount) {
  const connection = await db.getConnection();
  try {
    await connection.beginTransaction();
    
    await connection.query(
      'UPDATE accounts SET balance = balance - ? WHERE id = ?',
      [amount, senderId]
    );
    
    await connection.query(
      'UPDATE accounts SET balance = balance + ? WHERE id = ?',
      [amount, receiverId]
    );
    
    await connection.commit();
    return true;
  } catch (err) {
    await connection.rollback();
    throw err;
  } finally {
    connection.release();
  }
}

处理分页数据

async/await简化了分页数据的获取过程,可以顺序获取所有页面。

async function fetchAllPages(baseUrl) {
  let allData = [];
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await fetch(`${baseUrl}?page=${page}`);
    const { data, totalPages } = await response.json();
    allData = allData.concat(data);
    hasMore = page < totalPages;
    page++;
  }

  return allData;
}

实现重试逻辑

async/await使重试逻辑的实现变得简单直观。

async function fetchWithRetry(url, options = {}, retries = 3) {
  try {
    const response = await fetch(url, options);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.json();
  } catch (err) {
    if (retries <= 0) throw err;
    await new Promise(r => setTimeout(r, 1000 * (4 - retries))); // 指数退避
    return fetchWithRetry(url, options, retries - 1);
  }
}

与缓存策略的结合

async函数可以方便地实现缓存逻辑,减少不必要的重复请求。

const cache = new Map();

async function getWithCache(key, fetcher) {
  if (cache.has(key)) {
    return cache.get(key);
  }
  const data = await fetcher();
  cache.set(key, data);
  return data;
}

async function getUserWithCache(id) {
  return getWithCache(`user:${id}`, () => fetchUser(id));
}

处理并发限制

通过async函数可以实现并发控制,限制同时进行的异步操作数量。

class TaskQueue {
  constructor(concurrency) {
    this.concurrency = concurrency;
    this.running = 0;
    this.queue = [];
  }

  async runTask(task) {
    return new Promise((resolve, reject) => {
      this.queue.push(() => task().then(resolve, reject));
      this.next();
    });
  }

  async next() {
    while (this.running < this.concurrency && this.queue.length) {
      const task = this.queue.shift();
      this.running++;
      try {
        await task();
      } finally {

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:Promise原理与使用

下一篇:错误处理策略

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌