使用async function 定义异步函数

欢迎来到异步编程的奇妙世界:async function 详解

大家好,欢迎来到今天的讲座。今天我们要一起探讨的是 JavaScript 中非常重要的一个特性——async function。如果你已经对 Promiseasync/await 有了一些了解,那么今天的内容将会帮助你更深入地理解它们的工作原理和最佳实践。如果你是第一次接触这些概念,别担心,我会尽量用通俗易懂的语言来解释。

1. 异步编程的历史背景

在 JavaScript 的早期版本中,异步操作主要通过回调函数(Callback)来实现。还记得那些“回调地狱”吗?代码层层嵌套,难以阅读和维护。后来,Promise 出现了,它让异步操作变得更加结构化,但仍然不够简洁。直到 ES2017 引入了 async functionawait 关键字,JavaScript 的异步编程才真正迎来了质的飞跃。

回调地狱示例

function fetchData(callback) {
  setTimeout(() => {
    console.log("Fetching data...");
    callback(null, "Data fetched");
  }, 1000);
}

fetchData((err, data) => {
  if (err) {
    console.error(err);
  } else {
    console.log(data);
    // 继续嵌套更多回调...
  }
});

Promise 改进版

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("Fetching data...");
      resolve("Data fetched");
    }, 1000);
  });
}

fetchData()
  .then(data => {
    console.log(data);
    // 可以链式调用更多 then
  })
  .catch(err => {
    console.error(err);
  });

2. async function 的基本语法

async function 是一种声明异步函数的方式,它的返回值总是 Promise。你可以使用 await 关键字来等待 Promise 的结果,而不需要显式地使用 .then().catch()。这使得代码看起来更像是同步代码,更容易理解和维护。

基本语法

async function myAsyncFunction() {
  // 这里的代码可以包含 await 表达式
  const result = await somePromise();
  return result;
}

示例:使用 async/await

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}

fetchData();

注意事项

  • async function 总是返回一个 Promise,即使你没有显式地返回 Promise
  • 如果你在 async function 中返回一个非 Promise 值,JavaScript 会自动将其包装成一个已解决的 Promise
  • 使用 await 时,必须在 async function 内部。

3. async function 的工作原理

async function 实际上是基于 Promise 的语法糖。当你使用 async 关键字声明一个函数时,JavaScript 会自动将该函数的返回值包装成一个 Promise。如果函数内部抛出错误,Promise 会进入拒绝状态(rejected),否则会进入解决状态(resolved)。

async function 的内部机制

// 等价于以下代码
function myAsyncFunction() {
  return new Promise((resolve, reject) => {
    try {
      // 函数体中的代码
      const result = somePromise();
      result.then(resolve, reject);
    } catch (error) {
      reject(error);
    }
  });
}

await 的作用

await 用于暂停 async function 的执行,直到 Promise 被解决或拒绝。它不会阻塞整个程序的执行,只会暂停当前函数的执行,允许其他任务继续运行。

async function example() {
  console.log('Start');
  const result = await Promise.resolve('Done');
  console.log(result);
  console.log('End');
}

example();
// 输出:
// Start
// Done
// End

4. 并发与并行:Promise.all 与 async/await

虽然 await 让异步代码看起来像同步代码,但它有一个缺点:它是顺序执行的。也就是说,每个 await 表达式都会等待前一个 Promise 完成后才会继续执行。如果你想同时发起多个异步操作,可以使用 Promise.all 来并发执行它们。

顺序执行 vs 并发执行

// 顺序执行
async function sequential() {
  const result1 = await fetch('https://api.example.com/data1');
  const data1 = await result1.json();

  const result2 = await fetch('https://api.example.com/data2');
  const data2 = await result2.json();

  console.log(data1, data2);
}

// 并发执行
async function concurrent() {
  const [data1, data2] = await Promise.all([
    fetch('https://api.example.com/data1').then(res => res.json()),
    fetch('https://api.example.com/data2').then(res => res.json())
  ]);

  console.log(data1, data2);
}

Promise.all 的优势

  • 性能提升:并发执行多个异步操作可以显著减少总的等待时间。
  • 错误处理:如果其中一个 Promise 被拒绝,Promise.all 会立即拒绝,并返回第一个被拒绝的 Promise 的原因。

Promise.race 的应用场景

除了 Promise.all,还有 Promise.race,它会在第一个 Promise 被解决或拒绝时立即返回。这在某些场景下非常有用,例如设置超时:

async function fetchDataWithTimeout(url, timeout) {
  const fetchPromise = fetch(url);
  const timeoutPromise = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Request timed out')), timeout)
  );

  try {
    const response = await Promise.race([fetchPromise, timeoutPromise]);
    return await response.json();
  } catch (error) {
    throw error;
  }
}

5. 错误处理:try…catch 与 Promise.catch

async function 中,错误处理可以通过 try...catch 语句来实现。await 表达式抛出的任何错误都会被捕获到 catch 块中。这与 Promise.catch 的行为类似,但代码更加简洁。

try…catch 示例

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error fetching data:', error.message);
  }
}

Promise.catch 示例

function fetchData() {
  return fetch('https://api.example.com/data')
    .then(response => {
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return response.json();
    })
    .then(data => {
      console.log(data);
    })
    .catch(error => {
      console.error('Error fetching data:', error.message);
    });
}

错误传播

async function 中,未捕获的错误会传播到调用者。你可以通过 try...catchPromise.catch 来捕获这些错误。

async function outerFunction() {
  try {
    await innerFunction();
  } catch (error) {
    console.error('Error in outerFunction:', error);
  }
}

async function innerFunction() {
  throw new Error('Something went wrong');
}

6. 实战技巧:如何编写高效的异步代码

1. 避免过度使用 await

虽然 await 让代码看起来更简洁,但过度使用它可能会导致性能问题。尽量将多个异步操作并发执行,而不是顺序执行。

2. 使用 Promise.allSettled

有时你可能希望所有异步操作都完成,而不仅仅是第一个被解决或拒绝的操作。这时可以使用 Promise.allSettled,它会等待所有 Promise 都完成,并返回每个 Promise 的状态。

async function allOperations() {
  const results = await Promise.allSettled([
    fetch('https://api.example.com/data1'),
    fetch('https://api.example.com/data2')
  ]);

  results.forEach(result => {
    if (result.status === 'fulfilled') {
      console.log('Success:', result.value);
    } else {
      console.error('Error:', result.reason);
    }
  });
}

3. 处理长时间运行的任务

对于长时间运行的任务,考虑使用 AbortController 来取消请求。这可以帮助你避免不必要的资源浪费。

async function fetchDataWithAbortSignal(url, signal) {
  try {
    const response = await fetch(url, { signal });
    const data = await response.json();
    console.log(data);
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted');
    } else {
      console.error('Error fetching data:', error);
    }
  }
}

const controller = new AbortController();
const signal = controller.signal;

setTimeout(() => {
  controller.abort();
}, 5000);

fetchDataWithAbortSignal('https://api.example.com/data', signal);

7. 总结

今天我们学习了 async functionawait 的基本语法、工作原理以及一些实战技巧。async function 让异步代码更加简洁和易读,而 await 则让我们可以像写同步代码一样处理异步操作。掌握了这些知识,你可以在日常开发中写出更高效、更可靠的代码。

如果你有任何问题或想法,欢迎在评论区留言!下次再见,祝你编码愉快! 😊


参考资料:

  • MDN Web Docs: Asynchronous JavaScript
  • ECMAScript Specification: Async Functions

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注