深入理解JS中的异步函数

tianyi Lv2

首先明确一点,==异步函数不等于微任务==。

  1. 异步任务是JavaScript中一种在执行过程中不阻塞主线程的函数,一般用于处理耗时操作,比如网络请求,最后返回的是一个promise对象

  2. 而微任务则是JavaScript事件循环的一种任务类型,主要用于处理 Promise 的回调和 MutationObserver 的回调;并且拥有比宏任务更高的优先级,事件循环会首先处理所有微任务,然后再处理宏任务

  • 二者联系:异步函数会返回一个promise,而promise中的回调则是我们的微任务;而对于async和await,await关键字会暂停函数的执行,直到promise被解析或拒绝,这个过程中await后面的代码会被放入微任务队列中。

那异步函数到底是怎么执行的呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 定义一个函数来获取用户数据
function fetchUserData(userId) {
// 使用 fetch API 发起请求
return fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then(response => {
// 检查响应是否成功
if (!response.ok) {
throw new Error('Network response was not ok');
}
// 返回解析后的 JSON 数据
return response.json();
});
}

// 调用函数并处理返回的 Promise
fetchUserData(1)
.then(userData => {
// 输出用户数据
console.log('User Data:', userData);
})
.catch(error => {
// 处理错误
console.error('There was a problem with the fetch operation:', error);
});

// 主线程继续执行其他代码
console.log('Fetching user data...');

在 JavaScript 中,执行栈Call Stack任务队列Task Queue是理解异步操作执行机制的关键。我们可以通过分析上面提供的代码来了解它是如何执行的。

执行流程解析

  1. 主线程开始执行

    • 当代码开始执行时,JavaScript 引擎会将全局上下文推入执行栈。
  2. **调用 fetchUserData(1)**:

    • 主线程遇到 fetchUserData(1) 的调用,进入这个函数。
    • fetchUserData 函数内部,调用 fetch 方法发起网络请求。
    • fetch 是异步操作,返回一个 Promise。此时,fetch 的执行会被放入 Web API 环境中处理,主线程不会等待它完成,而是继续执行后面的代码。
  3. 返回到主线程

    • fetchUserData 函数返回 Promise 对象后,控制权回到主线程,执行栈中的 fetchUserData 函数被弹出。
    • 接下来,主线程继续执行后面的代码,输出 Fetching user data... 到控制台。
  4. 处理 Promise

    • fetch 请求完成时,它会调用相应的回调函数(即 then() 中的函数),这时会将这个回调函数放入微任务队列(Microtask Queue)。
    • 微任务队列优先于宏任务队列(Task Queue),所以在主线程空闲时,会优先执行微任务队列中的任务。
  5. 处理响应

    • 当主线程完成当前执行栈中的所有任务后,开始检查微任务队列。
    • 从微任务队列中取出 then() 中的回调函数,执行它。
    • 在这个回调中,首先检查响应是否成功,如果成功,则调用 response.json(),并返回解析后的 JSON 数据。
    • 然后,继续执行下一个 then() 中的回调,输出用户数据到控制台。
  6. 处理错误

    • 如果在任何步骤中发生错误(例如网络请求失败),则会调用 catch() 中的错误处理函数,输出错误信息到控制台。

执行栈与任务队列的关系

  • 执行栈

    • 主要用于同步代码的执行。当函数被调用时,它会被推入执行栈,执行完成后从栈中弹出。
  • 微任务队列

    • 存放 Promise 的回调和其他微任务。当主线程空闲时,会优先执行微任务队列中的任务。

总结

在这段代码的执行过程中,JavaScript 的事件循环机制确保了主线程不会被阻塞。尽管 fetch 是异步操作,但主线程在发起请求后继续执行后面的代码,保持响应能力。最终,网络请求完成后,相关的回调被放入微任务队列,并在主线程空闲时执行,从而处理异步操作的结果。这样,整个过程保证了代码的高效和非阻塞特性。

fetch原理

好的,让我们深入探讨一下“fetch 的执行会被放入 Web API 环境中处理”这句话的含义。

Web API 环境

在 JavaScript 中,尤其是在浏览器中,存在一个称为 Web API 的环境。这些 API 提供了与浏览器和操作系统的交互能力,例如处理 DOM、发起网络请求、定时器等。Web API 是在 JavaScript 引擎之外的环境中运行的。

fetch 的工作机制

  1. 异步调用

    • 当你调用 fetch(url) 时,JavaScript 引擎并不会立即执行这个请求,而是将这个请求的处理交给 Web API。此时,fetch 函数立即返回一个 Promise 对象,表示请求的未来结果。
  2. 请求的处理

    • Web API 接收到 fetch 请求后,会去执行实际的网络操作(例如,通过 HTTP 协议向服务器发送请求)。这个过程是异步的,意味着 JavaScript 引擎不会等待这个请求完成,而是继续执行后面的代码。
  3. Promise 的状态变化

    • 一旦网络请求完成(无论是成功还是失败),Web API 会将结果(例如,响应数据或错误信息)传递回 JavaScript 引擎。此时,Promise 的状态会被更新:
      • 如果请求成功,Promise 的状态变为“已解决”(fulfilled),并将响应对象作为值。
      • 如果请求失败,Promise 的状态变为“已拒绝”(rejected),并将错误信息作为值。
  4. 微任务队列

    • 当 Promise 的状态变化时,Web API 会将与之相关的回调(即 then()catch() 中的函数)放入微任务队列。这样,JavaScript 引擎可以在主线程空闲时执行这些微任务。

事件循环的角色

事件循环(Event Loop)是 JavaScript 的核心机制之一,它负责协调主线程和异步操作之间的执行。当主线程完成当前执行栈中的所有同步操作后,事件循环会检查微任务队列,优先执行其中的任务(如 then()catch() 中的回调)。因此,尽管 fetch 是异步的,最终结果会在主线程空闲时得到处理。

总结

综上所述,“fetch 的执行会被放入 Web API 环境中处理”意味着当你调用 fetch 时,实际的网络请求是由浏览器的 Web API 处理的,而不是在 JavaScript 引擎的执行栈中同步执行的。这种设计使得 JavaScript 能够高效地处理异步操作,保持主线程的响应能力,从而实现非阻塞的编程模型。

  • Title: 深入理解JS中的异步函数
  • Author: tianyi
  • Created at : 2025-04-26 10:46:35
  • Updated at : 2025-04-26 10:59:10
  • Link: https://github.com/ztygod/2025/04/26/深入理解JS中的异步函数/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments