深入理解 JavaScript 中 async 函数与 await 关键字的执行奥秘

深入理解 JavaScript 中 async 函数与 await 关键字的执行奥秘

前端开发中异步操作的重要性

在如今的前端开发领域,用户对于网页的交互体验要求越来越高。为了实现流畅且高效的交互效果,异步操作成为了前端工程师们不可或缺的 "秘密武器"。想象一下,当你在一个电商网站上浏览商品时,如果每次点击 "加载更多" 按钮都要等待页面完全卡死,直到所有新商品数据都加载完成后才恢复响应,那将会是多么糟糕的体验。这时候,异步操作就能派上用场,它可以让页面在等待数据加载的过程中,依然能够响应用户的其他操作,比如滚动页面、切换分类等。

在 JavaScript 中,我们有多种方式来处理异步操作,像回调函数、Promise 对象等。不过,ES7 引入的async函数和await关键字,为我们处理异步操作带来了更加优雅和便捷的解决方案,瞬间成为了前端工程师们的 "新宠"。接下来,咱们就一起深入探究一下async函数内部的执行流程,以及await关键字在其中扮演的关键角色。

从 JavaScript 的运行机制说起

在深入探讨async函数之前,咱们得先了解一下 JavaScript 的运行机制,因为这是理解async函数执行流程的基础。大家都知道,JavaScript 是一门单线程语言。啥意思呢?简单来说,就是在同一时间,JavaScript 只能执行一个任务。这就好比你吃饭的时候,同一时刻只能用一个勺子往嘴里送食物。

虽然 JavaScript 是单线程的,但在实际应用中,我们经常会遇到一些耗时的操作,比如网络请求获取数据、读取本地大文件等。要是这些操作都以同步的方式执行,那可就麻烦大了。比如,当网页发送一个网络请求去获取用户信息时,如果代码是同步执行的,那么在等待服务器响应的这段时间里,整个页面都会处于卡死状态,用户啥操作都干不了,这显然是我们不愿意看到的。 为了解决这个问题,JavaScript 引入了异步机制。那这个异步机制是怎么实现的呢?其实,JavaScript 把任务分成了两种类型:宏任务(macro - task)和微任务(micro - task)。

宏任务(macro - task)

常见的宏任务有整体代码script(也就是我们写的最外层的 JavaScript 代码块)、setTimeout、setInterval等。当 JavaScript 引擎开始执行代码时,会先把整体代码script作为第一个宏任务放入一个任务队列(也叫任务池)中。每当遇到一个新的宏任务,比如setTimeout设置的定时器回调函数,JavaScript 就会把它也放入这个任务队列中。

然后,JavaScript 引擎会按照任务放入队列的先后顺序,依次从任务队列中取出宏任务来执行。只有当前正在执行的宏任务执行完毕后,才会去任务队列中取下一个宏任务来执行。比如说,我们有这样一段代码:

javascript 复制代码
// 打印"开始执行script宏任务"
console.log('开始执行script宏任务'); 
// 设置一个定时器,2秒后执行回调函数
setTimeout(() => { 
  // 打印"setTimeout回调函数执行"
  console.log('setTimeout回调函数执行'); 
}, 2000);
// 打印"script宏任务执行完毕"
console.log('script宏任务执行完毕'); 

在这段代码中,首先会执行console.log('开始执行script宏任务');,这是整体代码script这个宏任务中的一部分。接着遇到setTimeout,它会被放入任务队列中,但并不会马上执行。然后继续执行console.log('script宏任务执行完毕');。直到script这个宏任务全部执行完,才会去检查任务队列中是否有新的宏任务。2 秒后,setTimeout的回调函数会被从任务队列中取出并执行,所以最终的打印顺序是:

javascript 复制代码
开始执行script宏任务
script宏任务执行完毕
setTimeout回调函数执行(2秒后)

微任务(micro - task)

微任务和宏任务有点类似,每个微任务也会被放入一个队列中。不过,微任务有个特殊的地方,它会绑定到当前正在执行的宏任务下面。也就是说,只有当前宏任务下的所有微任务都执行完毕后,这个宏任务才算真正执行结束,JavaScript 引擎才会去执行下一个宏任务。 常见的微任务有Promise对象的then回调函数、MutationObserver API、queueMicrotask()等。咱们来看个例子:

javascript 复制代码
// 打印"开始执行script宏任务"
console.log('开始执行script宏任务'); 
// 创建一个Promise对象,立即执行其回调函数
new Promise((resolve) => { 
  // 打印"Promise内部同步代码执行"
  console.log('Promise内部同步代码执行'); 
  // 调用resolve函数,将Promise状态变为resolved
  resolve(); 
})
// 当Promise状态变为resolved时,执行then回调函数,这是一个微任务
.then(() => { 
  // 打印"Promise then回调函数执行,这是微任务"
  console.log('Promise then回调函数执行,这是微任务'); 
});
// 打印"script宏任务执行完毕"
console.log('script宏任务执行完毕'); 

在这段代码中,首先执行console.log('开始执行script宏任务');,这是script宏任务的一部分。接着创建Promise对象,Promise内部的同步代码console.log('Promise内部同步代码执行');会立即执行,然后调用resolve函数,将Promise状态变为resolved。此时,then回调函数作为微任务被放入微任务队列中。继续执行console.log('script宏任务执行完毕');,当script宏任务执行完后,会先检查微任务队列。发现有Promise的then回调函数这个微任务,于是执行它,打印出Promise then回调函数执行,这是微任务。所以最终的打印顺序是:

javascript 复制代码
开始执行script宏任务
Promise内部同步代码执行
script宏任务执行完毕
Promise then回调函数执行,这是微任务

通过了解宏任务和微任务的执行规则,我们对 JavaScript 的异步运行机制有了更清晰的认识。这对于理解async函数内部的执行流程非常关键,因为async函数和await关键字的执行,都和宏任务、微任务有着千丝万缕的联系。

认识 async 函数

async函数是 ES7 引入的一个重要特性,它的出现让我们处理异步操作变得更加简单和直观。简单来说,async关键字用于声明一个异步函数,这个函数会返回一个Promise对象。

声明 async 函数

声明一个async函数非常简单,就像声明一个普通函数一样,只不过在函数定义前面加上async关键字。例如:

javascript 复制代码
// 声明一个名为fetchData的async函数
async function fetchData() { 
  // 函数体内部可以写异步操作的代码
}

这里的fetchData就是一个异步函数。当我们调用这个函数时,它会返回一个Promise对象。
async 函数的返回值
说到async函数的返回值,有几种情况需要我们注意。
情况一:返回一个普通值
当async函数返回一个普通值时,这个值会被自动包裹在一个已解决(resolved)状态的Promise对象中返回。比如:
async function getNumber() {
  // 返回一个普通数字123
  return 123; 
}
// 调用getNumber函数,它返回一个Promise对象
getNumber().then((result) => { 
  // 打印结果123
  console.log(result); 
});

在这段代码中,getNumber函数返回了一个普通数字123。当我们调用getNumber函数时,它返回的其实是一个状态为resolved,值为123的Promise对象。所以在then回调函数中,我们可以获取到这个值并打印出来。

情况二:返回一个 Promise 对象

如果async函数返回的是一个Promise对象,那么这个Promise对象的状态和值就会直接作为async函数返回的Promise对象的状态和值。例如:

javascript 复制代码
function createPromise() {
  // 返回一个Promise对象,2秒后将状态变为resolved,值为"成功啦"
  return new Promise((resolve) => { 
    setTimeout(() => {
      resolve('成功啦');
    }, 2000);
  });
}
async function getPromise() {
  // 返回createPromise函数创建的Promise对象
  return createPromise(); 
}
// 调用getPromise函数,它返回一个Promise对象
getPromise().then((result) => { 
  // 2秒后打印"成功啦"
  console.log(result); 
});

在这段代码中,createPromise函数返回一个Promise对象,getPromise这个async函数又返回了createPromise函数创建的Promise对象。所以当我们调用getPromise函数时,它返回的Promise对象的状态和值,就和createPromise函数返回的Promise对象的状态和值一样。2 秒后,Promise状态变为resolved,值为"成功啦",于是在then回调函数中打印出"成功啦"。

情况三:返回一个实现了 thenable 接口的对象

如果async函数返回一个对象,并且这个对象实现了thenable接口(也就是有一个then方法),那么async函数返回的Promise对象的状态和值,就会由这个对象的then方法来决定。比如说:

javascript 复制代码
const thenableObject = {
  // 定义then方法,接收resolve和reject两个函数作为参数
  then: function (resolve, reject) { 
    setTimeout(() => {
      // 2秒后调用resolve函数,将值设为"通过thenable对象返回的值"
      resolve('通过thenable对象返回的值'); 
    }, 2000);
  }
};
async function getThenable() {
  // 返回实现了thenable接口的对象thenableObject
  return thenableObject; 
}
// 调用getThenable函数,它返回一个Promise对象
getThenable().then((result) => { 
  // 2秒后打印"通过thenable对象返回的值"
  console.log(result); 
});

在这段代码中,thenableObject是一个实现了thenable接口的对象。getThenable这个async函数返回了这个对象。当我们调用getThenable函数时,它返回的Promise对象的状态和值,由thenableObject的then方法决定。2 秒后,then方法中的resolve函数被调用,值为"通过thenable对象返回的值",所以在then回调函数中打印出这个值。

通过上面几种情况,我们可以看出,async函数无论返回什么,最终都会返回一个Promise对象。这也使得我们可以用处理Promise的方式来处理async函数的返回值,为我们进行异步操作带来了极大的便利。

await 关键字的作用

await关键字是和async函数紧密配合使用的,它只能在async函数内部使用。await关键字的主要作用是暂停async函数的执行,直到它等待的那个异步操作完成(也就是对应的Promise对象变为resolved状态),然后获取这个异步操作的结果(也就是Promise对象的resolved值)。

await 等待一个 Promise 对象

当await后面跟着一个Promise对象时,async函数会在这里暂停执行,直到这个Promise对象的状态变为resolved,然后await会返回这个Promise对象的resolved值。咱们来看个例子:

javascript 复制代码
function fetchUserData() {
  // 返回一个Promise对象,模拟网络请求获取用户数据,1秒后将状态变为resolved,值为{ name: '张三', age: 25 }
  return new Promise((resolve) => { 
    setTimeout(() => {
      resolve({ name: '张三', age: 25 });
    }, 1000);
  });
}
async function displayUserData() {
  // await等待fetchUserData函数返回的Promise对象,暂停执行,1秒后获取到用户数据
  const user = await fetchUserData(); 
  // 打印用户数据{ name: '张三', age: 25 }
  console.log(user); 
}
// 调用displayUserData函数
displayUserData(); 

在这段代码中,fetchUserData函数返回一个Promise对象,模拟网络请求获取用户数据。在displayUserData这个async函数中,await fetchUserData();这行代码会暂停displayUserData函数的执行,直到fetchUserData函数返回的Promise对象状态变为resolved。1 秒后,Promise对象状态变为resolved,值为{ name: '张三', age: 25 },await获取到这个值并赋值给user变量,然后打印出用户数据。

await 等待一个普通值

如果await后面跟着的不是一个Promise对象,而是一个普通值,那么await会直接返回这个普通值,async函数也不会暂停执行。例如:

javascript 复制代码
async function printNumber() {
  // await直接返回普通数字456
  const num = await 456; 
  // 打印数字456
  console.log(num); 
}
// 调用printNumber函数
printNumber(); 

在这段代码中,await 456;会直接返回456,printNumber函数继续执行,打印出456。
await 与错误处理
在使用await时,我们还需要注意错误处理。因为await等待的Promise对象有可能会变为rejected状态,如果不处理这种情况,程序就会报错。我们可以使用try - catch语句来捕获await可能抛出的错误。比如:
function fetchUserData() {
  // 返回一个Promise对象,模拟网络请求失败,1秒后将状态变为rejected,值为"网络请求失败"
  return new Promise((_, reject) => { 
    setTimeout(() => {
      reject('网络请求失败');
    }, 1000);
  });
}
async function displayUserData() {
  try {
    // await等待fetchUserData函数返回的Promise对象,暂停执行,1秒后Promise变为rejected状态,抛出错误
    const user = await fetchUserData(); 
    // 如果Promise变为resolved状态,才会执行这里,由于上面Promise是rejected状态,这里不会执行
    console.log(user); 
  } catch (error) {
    // 捕获到错误,打印"错误信息:网络请求失败"
    console.log('错误信息:', error); 
  }
}
// 调用displayUserData函数
displayUserData(); 

在这段代码中,fetchUserData函数返回的Promise对象在 1 秒后变为rejected状态,值为"网络请求失败"。在displayUserData函数中,await fetchUserData();这行代码会抛出错误,这个错误被try - catch语句捕获,在catch块中打印出错误信息。 通过await关键字,我们可以以一种类似同步代码的方式来编写异步操作,让异步代码看起来更加简洁、易读。它就像是给异步操作加上了一个 "暂停键",让我们可以更好地控制异步操作的执行顺序和结果获取。

async 函数内部的执行流程详细剖析

了解了async函数和await关键字的基本概念后,接下来我们深入剖析一下async函数内部的执行流程。

没有 await 的 async 函数

当一个async函数内部没有await关键字时,它的执行流程其实和普通函数非常相似,都是按照代码的顺序依次执行。只不过async函数会返回一个Promise对象。例如:

javascript 复制代码
async function simpleAsyncFunction() {
  // 打印"开始执行async函数内部代码"
  console.log('开始执行async函数内部代码'); 
  // 打印"async函数内部代码执行完毕"
  console.log('async函数内部代码执行完毕'); 
  // 返回一个普通值"返回结果"
  return '返回结果'; 
}
// 调用simpleAsyncFunction函数,它返回一个Promise对象
simpleAsyncFunction().then((result) => { 
  // 打印"返回结果"
  console.log(result); 
});

在这段代码中,simpleAsyncFunction是一个async函数,内部没有await关键字。当我们调用这个函数时,首先会执行console.log('开始执行async函数内部代码');,然后执行console.log('async函数内部代码执行完毕');,最后返回"返回结果"。由于它是async函数,返回值会被包裹在一个已解决(resolved)状态的Promise对象中。所以在then回调函数中,我们可以获取到这个返回值并打印出来。整个过程就像普通函数一样,按照顺序执行,只是返回值的处理方式有所不同。

有 await 的 async 函数 当async函数内部有await关键字时,情况就变得稍微复杂一些了。我们来看一个具体的例子:

javascript 复制代码
function task1() {
  // 返回一个Promise对象,模拟一个耗时2秒的任务,2秒后将状态变为resolved,值为"任务1完成"
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('任务1完成');
    }, 2000);
  });
}

function task2() {
  // 返回一个Promise对象,模拟一个耗时1秒的任务,1秒后将状态变为resolved,值为"任务2完成"
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('任务2完成');
    }, 1000);
  });
}

async function main() {
  // 打印"开始执行async函数main"
  console.log('开始执行async函数main');
  // await等待task1函数返回的Promise对象,暂停执行,2秒后获取到结果
  const result1 = await task1();
  // 打印"任务1的结果:任务1完成"
  console.log('任务1的结果:', result1);
  // await等待task2函数返回的Promise对象,暂停执行,1秒后获取到结果
  const result2 = await task2();
  // 打印"任务2的结果:任务2完成"
  console.log('任务2的结果:', result2);
  // 打印"async函数main执行完毕"
  console.log('async函数main执行完毕');
  // 返回一个包含两个任务结果的数组
  return [result1, result2];
}

// 调用main函数,它返回一个Promise对象
main().then((finalResult) => {
  // 打印最终结果 ["任务1完成", "任务2完成"]
  console.log('最终结果:', finalResult);
});

在这个例子中,main是一个async函数,内部有两个await关键字。下面我们详细分析一下它的执行流程:

  1. 调用main函数 :当我们调用main函数时,它会返回一个Promise对象。此时,main函数内部的代码开始执行,首先打印出"开始执行async函数main"

  2. 遇到第一个await :执行到const result1 = await task1();时,await会暂停main函数的执行,同时task1函数会被调用。task1函数返回一个Promise对象,模拟一个耗时2秒的任务。在这2秒内,main函数会一直处于暂停状态,不会继续执行下面的代码。

  3. 第一个Promise对象状态变为resolved :2秒后,task1函数返回的Promise对象状态变为resolved,值为"任务1完成"await获取到这个值,并将其赋值给result1变量。此时,main函数恢复执行,打印出"任务1的结果: 任务1完成"

  4. 遇到第二个await :接着执行到const result2 = await task2();await再次暂停main函数的执行,task2函数被调用。task2函数返回一个Promise对象,模拟一个耗时1秒的任务。在这1秒内,main函数暂停执行。

  5. 第二个Promise对象状态变为resolved :1秒后,task2函数返回的Promise对象状态变为resolved,值为"任务2完成"await获取到这个值,并将其赋值给result2变量。main函数恢复执行,打印出"任务2的结果: 任务2完成"

  6. main函数执行完毕 :继续执行下面的代码,打印出"async函数main执行完毕",然后返回一个包含两个任务结果的数组[result1, result2]。由于mainasync函数,这个返回值会作为其返回的Promise对象的resolved值。

  7. 处理main函数返回的Promise对象 :在main().then((finalResult) => {...})中,then回调函数会在main函数返回的Promise对象状态变为resolved时执行,打印出最终结果["任务1完成", "任务2完成"]

从这个例子可以看出,await关键字让async函数的执行流程变得更像是同步代码,我们可以按照顺序依次处理多个异步任务,避免了使用回调函数嵌套带来的"回调地狱"问题。

结合宏任务和微任务理解执行流程

在实际的执行过程中,async函数和await关键字的执行还和宏任务、微任务的执行机制密切相关。我们来看一个更复杂的例子:

javascript 复制代码
// 打印"开始执行整体代码(宏任务)"
console.log('开始执行整体代码(宏任务)');

// 创建一个Promise对象,立即执行其回调函数
new Promise((resolve) => {
  // 打印"Promise内部同步代码执行"
  console.log('Promise内部同步代码执行');
  // 调用resolve函数,将Promise状态变为resolved
  resolve();
})
// 当Promise状态变为resolved时,执行then回调函数,这是一个微任务
.then(() => {
  // 打印"Promise then回调函数执行,这是微任务"
  console.log('Promise then回调函数执行,这是微任务');
});

// 定义一个async函数
async function asyncFunction() {
  // 打印"开始执行async函数内部代码"
  console.log('开始执行async函数内部代码');
  // 返回一个Promise对象,模拟一个耗时1秒的任务,1秒后将状态变为resolved,值为"async函数返回结果"
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('async函数返回结果');
    }, 1000);
  });
}

// 调用async函数,它返回一个Promise对象
asyncFunction().then((result) => {
  // 1秒后打印"async函数返回结果:async函数返回结果"
  console.log('async函数返回结果:', result);
});

// 打印"整体代码(宏任务)执行完毕"
console.log('整体代码(宏任务)执行完毕');

下面我们来分析一下这个例子的执行流程:

  1. 开始执行整体代码(宏任务) :首先打印出"开始执行整体代码(宏任务)"

  2. 执行Promise内部同步代码 :遇到new Promise,其内部的同步代码console.log('Promise内部同步代码执行');立即执行,然后调用resolve函数,将Promise状态变为resolved。此时,then回调函数作为微任务被放入微任务队列中。

  3. 调用asyncFunction函数 :调用asyncFunction函数,它返回一个Promise对象。asyncFunction函数内部的console.log('开始执行async函数内部代码');会立即执行,然后返回一个模拟耗时1秒的Promise对象。

  4. 继续执行整体代码 :打印出"整体代码(宏任务)执行完毕"。此时,当前宏任务执行完毕,开始检查微任务队列。

  5. 执行微任务 :发现微任务队列中有Promisethen回调函数,执行它,打印出"Promise then回调函数执行,这是微任务"

  6. 等待asyncFunction返回的Promise对象状态变化 :1秒后,asyncFunction返回的Promise对象状态变为resolved,其then回调函数被执行,打印出"async函数返回结果: async函数返回结果"

通过这个例子,我们可以更清楚地看到async函数和await关键字(这里虽然没有await,但async函数返回Promise对象的处理和有await时的原理相关)在宏任务和微任务的执行机制下是如何工作的。

前端开发中async函数和await关键字的实际应用场景

网络请求

在前端开发中,网络请求是非常常见的操作。比如从服务器获取用户信息、商品列表等。使用async函数和await关键字可以让我们更方便地处理网络请求的结果。例如,使用fetch API进行网络请求:

javascript 复制代码
async function fetchData() {
  try {
    // await等待fetch函数返回的Promise对象,暂停执行,直到请求完成
    const response = await fetch('https://api.example.com/data');
    // 检查响应状态是否为200
    if (!response.ok) {
      throw new Error('网络请求失败');
    }
    // await等待response.json()返回的Promise对象,暂停执行,直到解析JSON数据完成
    const data = await response.json();
    // 打印解析后的数据
    console.log('获取到的数据:', data);
    return data;
  } catch (error) {
    // 捕获到错误,打印错误信息
    console.error('请求出错:', error);
  }
}

// 调用fetchData函数
fetchData();

在这个例子中,fetchData是一个async函数。await fetch('https://api.example.com/data');会暂停函数执行,直到网络请求完成。如果请求成功,再使用await response.json();暂停函数执行,直到JSON数据解析完成。这样,我们可以以同步的方式处理异步的网络请求,代码看起来更加简洁易懂。

处理多个异步任务的顺序执行

有时候,我们需要按照一定的顺序依次执行多个异步任务。例如,先从服务器获取用户的基本信息,再根据这些信息获取用户的详细信息。使用async函数和await关键字可以很方便地实现这一点。

javascript 复制代码
function getUserBasicInfo() {
  // 返回一个Promise对象,模拟获取用户基本信息的请求,1秒后将状态变为resolved,值为{ id: 1, name: '李四' }
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id: 1, name: '李四' });
    }, 1000);
  });
}

function getUserDetailInfo(userId) {
  // 返回一个Promise对象,模拟获取用户详细信息的请求,1秒后将状态变为resolved,值为{ id: 1, age: 30, address: '北京市' }
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id: userId, age: 30, address: '北京市' });
    }, 1000);
  });
}

async function getFullUserInfo() {
  // await等待getUserBasicInfo函数返回的Promise对象,暂停执行,1秒后获取到用户基本信息
  const basicInfo = await getUserBasicInfo();
  // 打印用户基本信息 { id: 1, name: '李四' }
  console.log('用户基本信息:', basicInfo);
  // await等待getUserDetailInfo函数返回的Promise对象,暂停执行,1秒后获取到用户详细信息
  const detailInfo = await getUserDetailInfo(basicInfo.id);
  // 打印用户详细信息 { id: 1, age: 30, address: '北京市' }
  console.log('用户详细信息:', detailInfo);
  // 返回一个包含用户基本信息和详细信息的对象
  return { ...basicInfo, ...detailInfo };
}

// 调用getFullUserInfo函数
getFullUserInfo().then((fullInfo) => {
  // 打印用户完整信息 { id: 1, name: '李四', age: 30, address: '北京市' }
  console.log('用户完整信息:', fullInfo);
});

在这个例子中,getFullUserInfo是一个async函数。它先使用await等待getUserBasicInfo函数返回的Promise对象,获取用户基本信息。然后根据基本信息中的id,使用await等待getUserDetailInfo函数返回的Promise对象,获取用户详细信息。这样就实现了多个异步任务的顺序执行。

处理动画效果的顺序播放

在前端开发中,有时候我们需要按照一定的顺序依次播放多个动画效果。使用async函数和await关键字可以很好地控制动画的播放顺序。例如,使用CSS动画和JavaScript结合:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    .box {
      width: 100px;
      height: 100px;
      background-color: blue;
      margin-bottom: 20px;
    }

    @keyframes slideIn {
      from {
        transform: translateX(-100%);
      }
      to {
        transform: translateX(0);
      }
    }

    @keyframes fadeIn {
      from {
        opacity: 0;
      }
      to {
        opacity: 1;
      }
    }
  </style>
</head>

<body>
  <div class="box" id="box1"></div>
  <div class="box" id="box2"></div>
  <script>
    async function playAnimations() {
      const box1 = document.getElementById('box1');
      const box2 = document.getElementById('box2');

      // 定义一个函数,返回一个Promise对象,当动画结束时将状态变为resolved
      function animate(element, animationName, duration) {
        return new Promise((resolve) => {
          element.style.animation = `${animationName} ${duration}s forwards`;
          element.addEventListener('animationend', () => {
            resolve();
          }, { once: true });
        });
      }

      // await等待box1的slideIn动画完成
      await animate(box1, 'slideIn', 1);
      // await等待box2的fadeIn动画完成
      await animate(box2, 'fadeIn', 1);
      console.log('所有动画播放完毕');
    }

    // 调用playAnimations函数
    playAnimations();
  </script>
</body>

</html>

在这个例子中,playAnimations是一个async函数。它定义了一个animate函数,用于创建一个Promise对象,当动画结束时将该Promise对象的状态变为resolved。然后使用await依次等待box1slideIn动画和box2fadeIn动画完成,从而实现了动画效果的顺序播放。

总结

async函数和await关键字是JavaScript中处理异步操作的强大工具。async函数返回一个Promise对象,让我们可以用处理Promise的方式来处理其返回值。await关键字则可以暂停async函数的执行,直到等待的异步操作完成,以一种类似同步代码的方式编写异步代码,避免了"回调地狱"问题。

通过深入理解async函数内部的执行流程,以及结合宏任务、微任务的执行机制,我们可以更好地控制异步操作的执行顺序和结果获取。在前端开发中,async函数和await关键字在网络请求、处理多个异步任务的顺序执行、处理动画效果的顺序播放等场景中都有广泛的应用。

希望通过本文的内容介绍,你对async函数和await关键字有了更深入的理解,能够在实际开发中更加熟练地运用它们,编写出更加简洁、高效的异步代码。

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
yunteng5218 小时前
通用架构(同城双活)(单点接入)
架构·同城双活·单点接入