阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > async/await异步编程

async/await异步编程

作者:陈川 阅读数:14208人阅读 分类: JavaScript

ECMAScript 8 async/await异步编程

ECMAScript 8(ES2017)引入了async/await语法,让基于Promise的异步代码看起来更像同步代码。这种语法糖彻底改变了JavaScript异步编程的方式,使代码更易读、更易维护。

async函数基础

async函数是使用async关键字声明的函数,它总是返回一个Promise。如果在async函数中返回一个非Promise值,JavaScript会自动将其包装为已解决的Promise。

async function basicAsync() {
  return 'Hello Async';
}

basicAsync().then(console.log); // 输出: "Hello Async"

async函数内部可以使用await关键字暂停执行,直到等待的Promise解决或拒绝。await只能在async函数内部使用。

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

await表达式详解

await表达式会暂停async函数的执行,等待Promise解决,然后恢复async函数的执行并返回解决的值。如果等待的值不是Promise,它会被转换为一个已解决的Promise。

async function awaitDemo() {
  const result = await new Promise(resolve => {
    setTimeout(() => resolve('Done!'), 1000);
  });
  console.log(result); // 1秒后输出: "Done!"
}

当Promise被拒绝时,await会抛出拒绝的原因,可以使用try/catch捕获:

async function handleError() {
  try {
    await Promise.reject('Something went wrong');
  } catch (error) {
    console.error('Caught error:', error);
  }
}

并行执行多个异步操作

虽然await会顺序执行,但我们可以通过一些技巧实现并行:

async function parallelTasks() {
  const [user, posts] = await Promise.all([
    fetch('/user'),
    fetch('/posts')
  ]);
  
  // 两个请求同时进行,都完成后才继续执行
}

另一种方式是先启动所有Promise,然后再await:

async function parallelAwait() {
  const userPromise = fetch('/user');
  const postsPromise = fetch('/posts');
  
  const user = await userPromise;
  const posts = await postsPromise;
}

错误处理模式

async/await提供了多种错误处理方式:

  1. 传统的try/catch:
async function tryCatchExample() {
  try {
    const data = await fetchData();
  } catch (error) {
    console.error('Fetch failed:', error);
  }
}
  1. 在Promise链末尾添加catch:
async function promiseCatchExample() {
  const data = await fetchData().catch(error => {
    console.error('Fetch failed:', error);
  });
}
  1. 使用辅助函数包装:
function to(promise) {
  return promise.then(data => [null, data]).catch(err => [err]);
}

async function usingHelper() {
  const [err, data] = await to(fetchData());
  if (err) console.error('Error:', err);
}

async函数与Promise的互操作性

async函数返回Promise,因此可以无缝与现有Promise代码配合使用:

async function getUser(id) {
  return { id, name: 'John Doe' };
}

// 在Promise链中使用async函数
getUser(1)
  .then(user => console.log(user))
  .catch(console.error);

// 在async函数中使用Promise
async function processUser() {
  const user = await getUser(1);
  console.log(user);
}

常见陷阱与最佳实践

  1. 避免不必要的await
// 不好 - 不必要的await
async function slowFunc() {
  const result = await doSomething();
  return await processResult(result);
}

// 更好
async function fastFunc() {
  const result = await doSomething();
  return processResult(result); // processResult返回Promise
}
  1. 循环中的await
// 顺序执行 - 每个await都会等待前一个完成
async function processArray(array) {
  for (const item of array) {
    await processItem(item);
  }
}

// 并行执行
async function processArrayParallel(array) {
  await Promise.all(array.map(item => processItem(item)));
}
  1. 顶层await:在ES模块中可以直接使用await
// module.js
const data = await fetchData();
export default data;

高级模式

  1. 重试逻辑
async function retry(fn, retries = 3, delay = 1000) {
  try {
    return await fn();
  } catch (error) {
    if (retries <= 0) throw error;
    await new Promise(resolve => setTimeout(resolve, delay));
    return retry(fn, retries - 1, delay * 2);
  }
}
  1. 超时控制
async function withTimeout(promise, timeout) {
  let timer;
  const timeoutPromise = new Promise((_, reject) => {
    timer = setTimeout(() => reject(new Error('Timeout')), timeout);
  });
  
  try {
    return await Promise.race([promise, timeoutPromise]);
  } finally {
    clearTimeout(timer);
  }
}
  1. 顺序处理动态生成的Promise
async function processInOrder(generator) {
  for await (const promise of generator) {
    try {
      const result = await promise;
      console.log(result);
    } catch (error) {
      console.error(error);
    }
  }
}

async函数与类方法

async函数也可以作为类的方法:

class ApiClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }
  
  async get(endpoint) {
    const response = await fetch(`${this.baseUrl}${endpoint}`);
    return response.json();
  }
}

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

async生成器函数

ES2018引入了异步生成器函数,可以yield Promise:

async function* asyncGenerator() {
  let i = 0;
  while (i < 3) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    yield i++;
  }
}

(async () => {
  for await (const num of asyncGenerator()) {
    console.log(num); // 每秒输出0, 1, 2
  }
})();

性能考量

虽然async/await使代码更清晰,但需要注意:

  1. 每个await都会导致函数暂停,创建额外的Promise对象
  2. 不必要的顺序await会降低性能
  3. 错误堆栈可能不如同步代码清晰
// 性能较差的例子
async function poorPerformance() {
  const a = await taskA(); // 等待A完成
  const b = await taskB(); // 然后等待B完成
  return a + b;
}

// 改进版本
async function betterPerformance() {
  const [a, b] = await Promise.all([taskA(), taskB()]);
  return a + b;
}

浏览器兼容性与转译

虽然现代浏览器都支持async/await,但在旧环境中需要使用Babel等工具转译:

// 转译前的async/await
async function fetchUser() {
  const response = await fetch('/user');
  return response.json();
}

// 转译后的代码大致如下
function fetchUser() {
  return Promise.resolve().then(function() {
    return fetch('/user');
  }).then(function(response) {
    return response.json();
  });
}

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

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

前端川

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