欢迎来到异步编程的奇妙世界:async function 详解
大家好,欢迎来到今天的讲座。今天我们要一起探讨的是 JavaScript 中非常重要的一个特性——async function
。如果你已经对 Promise
和 async/await
有了一些了解,那么今天的内容将会帮助你更深入地理解它们的工作原理和最佳实践。如果你是第一次接触这些概念,别担心,我会尽量用通俗易懂的语言来解释。
1. 异步编程的历史背景
在 JavaScript 的早期版本中,异步操作主要通过回调函数(Callback)来实现。还记得那些“回调地狱”吗?代码层层嵌套,难以阅读和维护。后来,Promise
出现了,它让异步操作变得更加结构化,但仍然不够简洁。直到 ES2017 引入了 async function
和 await
关键字,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...catch
或 Promise.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 function
和 await
的基本语法、工作原理以及一些实战技巧。async function
让异步代码更加简洁和易读,而 await
则让我们可以像写同步代码一样处理异步操作。掌握了这些知识,你可以在日常开发中写出更高效、更可靠的代码。
如果你有任何问题或想法,欢迎在评论区留言!下次再见,祝你编码愉快! 😊
参考资料:
- MDN Web Docs: Asynchronous JavaScript
- ECMAScript Specification: Async Functions