Skip to content

async/await

async/await是ES2017(ES8)引入的一种新的异步编程语法,它基于Promise,提供了一种更简洁、更直观的方式来编写异步代码。async/await使得异步代码看起来更像同步代码,大大提高了代码的可读性和可维护性,成为现代JavaScript中处理异步操作的首选方式。

async/await的基本语法

async函数

async关键字用于声明一个异步函数,它可以出现在函数声明、函数表达式、箭头函数和方法中。

javascript
// 函数声明
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来捕获。

javascript
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将以抛出的异常为结果
javascript
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对象完成:

  1. 暂停async函数的执行
  2. 执行await后面的表达式,得到一个Promise对象
  3. 等待Promise对象完成(成功或失败)
  4. 如果Promise成功,将其结果作为await表达式的结果,继续执行async函数
  5. 如果Promise失败,将其错误作为异常抛出,需要使用try/catch来捕获
javascript
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使得处理单个异步操作更加简洁直观:

javascript
// 使用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使得处理多个串行异步操作更加清晰:

javascript
// 使用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可以处理多个并行异步操作:

javascript
// 使用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使得处理异步循环更加直观:

javascript
// 串行执行
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来实现:

javascript
// 使用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的优缺点

优点

  1. 代码可读性高:async/await使得异步代码看起来更像同步代码,大大提高了代码的可读性
  2. 错误处理方便:使用try/catch可以方便地捕获异步操作的错误
  3. 调试友好:async/await使得调试更加直观,因为代码执行流程更加线性
  4. 代码结构清晰:async/await避免了Promise链的嵌套,使得代码结构更加清晰
  5. 易于理解:对于新手来说,async/await比Promise更容易理解和使用

缺点

  1. 浏览器兼容性:虽然现代浏览器都支持async/await,但旧版本浏览器可能需要使用Babel等工具进行转译
  2. 错误处理需要注意:如果忘记使用try/catch,async函数中的错误可能会被静默忽略
  3. 性能考虑:在某些场景下,async/await可能会比原生Promise慢一些,但在大多数情况下,这种差异可以忽略不计
  4. 可能导致死锁:如果在非async函数中使用await,或者在async函数中使用了不正确的await顺序,可能会导致死锁

async/await与Promise的对比

特性async/awaitPromise
代码风格同步风格,易读性高链式调用,易读性中等
错误处理使用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可以处理多个并行异步操作:

javascript
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函数中的错误:

javascript
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中处理异步操作的首选方式。它在处理单个异步操作、多个串行异步操作、多个并行异步操作等场景下都非常实用,是面试中经常被问到的重要知识点。

好好学习,天天向上