async/await
async/await是ES2017(ES8)引入的一种新的异步编程语法,它基于Promise,提供了一种更简洁、更直观的方式来编写异步代码。async/await使得异步代码看起来更像同步代码,大大提高了代码的可读性和可维护性,成为现代JavaScript中处理异步操作的首选方式。
async/await的基本语法
async函数
async关键字用于声明一个异步函数,它可以出现在函数声明、函数表达式、箭头函数和方法中。
// 函数声明
async function fetchData() {
// 函数体
}
// 函数表达式
const fetchData = async function() {
// 函数体
};
// 箭头函数
const fetchData = async () => {
// 函数体
};
// 方法
const obj = {
async fetchData() {
// 函数体
}
};
// 类方法
class MyClass {
async fetchData() {
// 函数体
}
}await表达式
await关键字用于等待一个Promise对象的完成,它只能在async函数中使用。await表达式会暂停async函数的执行,直到Promise对象完成(成功或失败),然后返回Promise的结果。如果Promise失败,await会抛出异常,需要使用try/catch来捕获。
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.log(error);
throw error;
}
}
fetchData();async/await的工作原理
async函数的返回值
async函数总是返回一个Promise对象:
- 如果async函数中没有return语句,返回的Promise将以undefined为结果
- 如果async函数中有return语句,返回的Promise将以return的值为结果
- 如果async函数中抛出异常,返回的Promise将以抛出的异常为结果
async function foo() {
return 'Hello';
}
const promise = foo();
console.log(promise); // 输出: Promise { 'Hello' }
promise.then(result => {
console.log(result); // 输出: Hello
});
async function bar() {
throw new Error('Error occurred');
}
const promise2 = bar();
console.log(promise2); // 输出: Promise { <rejected> Error: Error occurred }
promise2.catch(error => {
console.log(error); // 输出: Error: Error occurred
});await的工作机制
当遇到await表达式时,async函数会暂停执行,直到Promise对象完成:
- 暂停async函数的执行
- 执行await后面的表达式,得到一个Promise对象
- 等待Promise对象完成(成功或失败)
- 如果Promise成功,将其结果作为await表达式的结果,继续执行async函数
- 如果Promise失败,将其错误作为异常抛出,需要使用try/catch来捕获
async function fetchData() {
console.log('Start fetching data');
// 暂停执行,等待Promise完成
const response = await fetch('https://api.example.com/data');
console.log('Response received');
// 暂停执行,等待Promise完成
const data = await response.json();
console.log('Data parsed');
return data;
}
fetchData().then(data => {
console.log('Data:', data);
});
console.log('After calling fetchData');
// 输出顺序:
// Start fetching data
// After calling fetchData
// Response received
// Data parsed
// Data: { ... }async/await的应用场景
1. 处理单个异步操作
async/await使得处理单个异步操作更加简洁直观:
// 使用Promise
function fetchData() {
return fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => {
console.log(data);
return data;
})
.catch(error => {
console.log(error);
throw error;
});
}
// 使用async/await
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.log(error);
throw error;
}
}2. 处理多个串行异步操作
async/await使得处理多个串行异步操作更加清晰:
// 使用Promise
function fetchUserAndPosts() {
return fetch('https://api.example.com/user')
.then(response => response.json())
.then(user => {
return fetch(`https://api.example.com/posts?userId=${user.id}`)
.then(response => response.json())
.then(posts => {
return { user, posts };
});
});
}
// 使用async/await
async function fetchUserAndPosts() {
try {
const userResponse = await fetch('https://api.example.com/user');
const user = await userResponse.json();
const postsResponse = await fetch(`https://api.example.com/posts?userId=${user.id}`);
const posts = await postsResponse.json();
return { user, posts };
} catch (error) {
console.log(error);
throw error;
}
}3. 处理多个并行异步操作
使用Promise.all()结合async/await可以处理多个并行异步操作:
// 使用Promise
function fetchMultipleData() {
return Promise.all([
fetch('https://api.example.com/data1'),
fetch('https://api.example.com/data2'),
fetch('https://api.example.com/data3')
])
.then(responses => {
return Promise.all(responses.map(response => response.json()));
})
.then(data => {
console.log(data);
return data;
});
}
// 使用async/await
async function fetchMultipleData() {
try {
const [response1, response2, response3] = await Promise.all([
fetch('https://api.example.com/data1'),
fetch('https://api.example.com/data2'),
fetch('https://api.example.com/data3')
]);
const [data1, data2, data3] = await Promise.all([
response1.json(),
response2.json(),
response3.json()
]);
console.log(data1, data2, data3);
return [data1, data2, data3];
} catch (error) {
console.log(error);
throw error;
}
}4. 处理异步循环
async/await使得处理异步循环更加直观:
// 串行执行
async function processItems(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
// 并行执行
async function processItems(items) {
const promises = items.map(item => processItem(item));
const results = await Promise.all(promises);
return results;
}
// 限制并发数
async function processItems(items, concurrencyLimit) {
const results = [];
const executing = [];
for (const item of items) {
const promise = processItem(item);
results.push(promise);
const executingPromise = promise.then(() => {
executing.splice(executing.indexOf(executingPromise), 1);
});
executing.push(executingPromise);
if (executing.length >= concurrencyLimit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}5. 与Generator函数结合
async/await可以看作是Generator函数的语法糖,它内部使用了Generator函数和Promise来实现:
// 使用Generator函数
function* fetchData() {
yield fetch('https://api.example.com/data');
yield fetch('https://api.example.com/data2');
}
// 使用async/await
async function fetchData() {
await fetch('https://api.example.com/data');
await fetch('https://api.example.com/data2');
}async/await的优缺点
优点
- 代码可读性高:async/await使得异步代码看起来更像同步代码,大大提高了代码的可读性
- 错误处理方便:使用try/catch可以方便地捕获异步操作的错误
- 调试友好:async/await使得调试更加直观,因为代码执行流程更加线性
- 代码结构清晰:async/await避免了Promise链的嵌套,使得代码结构更加清晰
- 易于理解:对于新手来说,async/await比Promise更容易理解和使用
缺点
- 浏览器兼容性:虽然现代浏览器都支持async/await,但旧版本浏览器可能需要使用Babel等工具进行转译
- 错误处理需要注意:如果忘记使用try/catch,async函数中的错误可能会被静默忽略
- 性能考虑:在某些场景下,async/await可能会比原生Promise慢一些,但在大多数情况下,这种差异可以忽略不计
- 可能导致死锁:如果在非async函数中使用await,或者在async函数中使用了不正确的await顺序,可能会导致死锁
async/await与Promise的对比
| 特性 | async/await | Promise |
|---|---|---|
| 代码风格 | 同步风格,易读性高 | 链式调用,易读性中等 |
| 错误处理 | 使用try/catch,直观 | 使用catch()方法,链式 |
| 调试 | 断点调试方便 | 断点调试较复杂 |
| 并发处理 | 结合Promise.all() | 直接使用Promise.all() |
| 浏览器兼容性 | 需要ES2017支持 | 需要ES6支持 |
| 代码行数 | 较少 | 较多 |
| 学习曲线 | 较平缓 | 较陡峭 |
面试常见问题
1. 什么是async/await?它与Promise有什么关系?
- async/await的定义:async/await是ES2017引入的一种新的异步编程语法,它基于Promise,提供了一种更简洁、更直观的方式来编写异步代码
- 与Promise的关系:async/await是Promise的语法糖,它内部使用了Promise来实现异步操作的处理
2. async函数的返回值是什么?
async函数总是返回一个Promise对象:
- 如果async函数中没有return语句,返回的Promise将以undefined为结果
- 如果async函数中有return语句,返回的Promise将以return的值为结果
- 如果async函数中抛出异常,返回的Promise将以抛出的异常为结果
3. await关键字的作用是什么?它只能在什么地方使用?
- await的作用:await关键字用于等待一个Promise对象的完成,它会暂停async函数的执行,直到Promise对象完成(成功或失败),然后返回Promise的结果
- 使用限制:await只能在async函数中使用,否则会抛出语法错误
4. 如何使用async/await处理多个并行异步操作?
使用Promise.all()结合async/await可以处理多个并行异步操作:
async function fetchMultipleData() {
try {
const [response1, response2, response3] = await Promise.all([
fetch('https://api.example.com/data1'),
fetch('https://api.example.com/data2'),
fetch('https://api.example.com/data3')
]);
const [data1, data2, data3] = await Promise.all([
response1.json(),
response2.json(),
response3.json()
]);
return [data1, data2, data3];
} catch (error) {
console.log(error);
throw error;
}
}5. async/await的错误处理方式是什么?
使用try/catch来捕获async函数中的错误:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (error) {
console.log(error);
throw error;
}
}6. async/await与Generator函数的关系是什么?
async/await可以看作是Generator函数的语法糖,它内部使用了Generator函数和Promise来实现。async函数相当于一个自执行的Generator函数,它会自动执行Generator函数的next()方法,直到Generator函数结束。
总结
async/await是ES2017引入的一种新的异步编程语法,它基于Promise,提供了一种更简洁、更直观的方式来编写异步代码。async/await使得异步代码看起来更像同步代码,大大提高了代码的可读性和可维护性,成为现代JavaScript中处理异步操作的首选方式。它在处理单个异步操作、多个串行异步操作、多个并行异步操作等场景下都非常实用,是面试中经常被问到的重要知识点。