深入理解 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关键字有了更深入的理解,能够在实际开发中更加熟练地运用它们,编写出更加简洁、高效的异步代码。

相关推荐
满怀101517 分钟前
【Vue 3全栈实战】从响应式原理到企业级架构设计
前端·javascript·vue.js·vue
luckywuxn27 分钟前
使用gitbook 工具编写接口文档或博客
前端
梅子酱~1 小时前
Vue 学习随笔系列二十三 -- el-date-picker 组件
前端·vue.js·学习
伟笑1 小时前
elementUI 循环出来的表单,怎么做表单校验?
前端·javascript·elementui
辣辣y1 小时前
React中useMemo和useCallback的作用:
前端·react
Alice-YUE1 小时前
【HTML5学习笔记1】html标签(上)
前端·笔记·学习·html·html5
Alice-YUE1 小时前
【HTML5学习笔记2】html标签(下)
前端·笔记·html·html5
确实菜,真的爱1 小时前
electron进程通信
前端·javascript·electron
好吃的肘子1 小时前
Elasticsearch架构原理
开发语言·算法·elasticsearch·架构·jenkins
编程星空1 小时前
架构与UML4+1视图
架构