当前位置: 首页 > backend >正文

js代码03

题目

好的,我们进入 JavaScript 的另一个核心领域:异步编程

在之前的练习中,我们写的代码都是同步 (Synchronous) 的,也就是说,代码从上到下一行一行地执行,上一行没执行完,下一行就绝不会开始。

但现实世界是异步 (Asynchronous) 的。比如,我们向服务器请求数据,这个过程可能需要几百毫-秒甚至几秒钟。如果我们的程序傻傻地“同步”等待,那么在数据返回之前,整个页面(比如网页上的按钮、动画)都会被卡住,无法响应用户操作。这就是所谓的“阻塞 (Blocking)”。

为了解决这个问题,JavaScript 采用了异步编程模型。


练习 03: 异步的初步体验 - 回调与 setTimeout

这个练习将带你初次体验异步操作,并了解早期处理异步的一种方式:回调函数 (Callback Functions)

🎯 学习目标:

  • 理解同步与异步代码执行流程的区别。
  • 学会使用 setTimeout 来模拟一个耗时操作。
  • 理解并使用回调函数来处理异步操作的结果。
  • (可选) 初步感受“回调地狱 (Callback Hell)”是什么样的。

背景知识:

  • setTimeout(callback, delay): 这是浏览器和 Node.js 都提供的一个全局函数。它接受两个参数:
    1. callback: 一个函数,这个函数将会在指定的延迟时间之后被执行。
    2. delay: 一个以毫秒为单位的数字,表示要延迟多久才执行 callback 函数。
  • 事件循环 (Event Loop): 这是 JavaScript 异步机制的核心。你现在只需要知道:当你调用 setTimeout 时,JavaScript 引擎并不会停在那里等待,而是会把这个“任务”(即你的回调函数)交给它的一个“助手”(比如浏览器内核)。然后,JS 引擎会继续执行后面的同步代码。当指定的延迟时间到了,助手就会把这个任务放回一个“任务队列”中,等待 JS 引擎空闲时再去执行它。

🛠️ 任务:
我们要模拟一个“获取用户数据”的场景。这个操作通常是异步的,因为它需要时间去网络上请求。

  1. 创建一个名为 fetchUserData 的函数,它接受两个参数:
    • userId (一个数字,代表用户ID)。
    • callback (一个函数,当数据“获取”成功后,我们用它来处理数据)。
  2. fetchUserData 函数内部,使用 setTimeout 来模拟一个 2 秒钟的网络延迟。
  3. 在 2 秒钟后,setTimeout 的回调函数应该:
    • 创建一个模拟的用户数据对象,例如 { id: userId, name: 'John Doe', email: 'john.doe@example.com' }
    • 调用我们作为参数传入的 callback 函数,并将这个模拟的用户数据对象作为参数传给它。
  4. 在主代码中,调用 fetchUserData 函数,并提供一个回调函数来打印获取到的用户信息。
  5. 在调用 fetchUserData 之后,立即打印一条消息,如 “正在获取用户数据,请稍候…”,以观察同步和异步代码的执行顺序。

📋 初始代码:
创建新文件 03-callbacks.js,并复制以下代码。

console.log("程序开始执行...");/*** 模拟从服务器获取用户数据。* @param {number} userId - 要获取的用户的ID。* @param {function(object): void} callback - 数据获取成功后要执行的回调函数。*/
function fetchUserData(userId, callback) {// --- 在这里编写你的代码 ---// 1. 使用 setTimeout 模拟网络延迟。// 2. 在 setTimeout 的回调中,创建一个假的用户对象。// 3. 调用传入的 callback 函数,并把用户对象作为参数传进去。}// 调用 fetchUserData,并传入一个回调函数来处理结果
fetchUserData(123, (user) => {console.log("成功获取到用户数据!");console.log(`用户姓名: ${user.name}`);console.log(`用户邮箱: ${user.email}`);
});console.log("程序继续执行... (这行代码会先于用户数据被打印)");

✅ 预期输出:
运行代码后,你应该会看到这样的输出顺序:

  1. 程序开始执行... (立刻打印)
  2. 程序继续执行... (这行代码会先于用户数据被打印) (立刻打印)
  3. (等待大约 2 秒钟)
  4. 成功获取到用户数据! (2秒后打印)
  5. 用户姓名: John Doe (2秒后打印)
  6. 用户邮箱: john.doe@example.com (2秒后打印)

这个输出顺序完美地展示了异步编程的非阻塞特性。程序不会傻等 fetchUserData 完成,而是会继续执行后面的代码。

这个练习是理解后续 Promise、async/await 的基石。开始吧!

答案

当然,我们一起来看这个实现,并深入分析代码的执行流程,这对于理解异步至关重要。

参考实现 (03-callbacks.js)

console.log("程序开始执行...");/*** 模拟从服务器获取用户数据。* @param {number} userId - 要获取的用户的ID。* @param {function(object): void} callback - 数据获取成功后要执行的回调函数。*/
function fetchUserData(userId, callback) {// 1. 使用 setTimeout 模拟一个 2000 毫秒(2秒)的网络延迟。setTimeout(() => {// 这部分代码将会在 2 秒后执行...console.log("...网络请求耗时2秒,现在返回数据...");// 2. 创建一个假的用户对象。const user = {id: userId,name: 'John Doe',email: 'john.doe@example.com'};// 3. 调用我们当初传入的 callback 函数,并把用户对象作为参数传进去。callback(user);}, 2000); // 延迟 2000 毫秒
}// 调用 fetchUserData,并传入一个回调函数来处理结果
fetchUserData(123, (user) => {console.log("成功获取到用户数据!");console.log(`用户姓名: ${user.name}`);console.log(`用户邮箱: ${user.email}`);
});console.log("程序继续执行... (这行代码会先于用户数据被打印)");

代码执行流程全解析

这正是异步编程最有趣也最反直觉的地方。让我们一步步追踪 JavaScript 引擎的“思考过程”:

  1. 第 1 步: 引擎看到 console.log("程序开始执行...");。这是一个同步任务,立刻执行。

    • 控制台输出: 程序开始执行...
  2. 第 2 步: 引擎看到 fetchUserData(123, (user) => { ... }); 这个函数调用。它立刻进入 fetchUserData 函数内部。

    • userId 参数被设为 123
    • callback 参数被设为我们定义的那个箭头函数 (user) => { ... }
  3. 第 3 步 (最关键的一步): 在 fetchUserData 函数内部,引擎遇到了 setTimeout(...)

    • setTimeout 本质上是在对 JavaScript 引擎说:“嘿,这里有一个任务(() => { ... } 这个函数),请你帮我设置一个 2 秒的定时器。时间到了之后,再把这个任务放到待办列表里去执行。现在你不用管它,请继续往下走!
    • 于是,JavaScript 引擎设置好了定时器,然后就立刻离开了 fetchUserData 函数,继续执行后面的代码。它不会在这里傻等 2 秒钟。
  4. 第 4 步: 引擎执行 fetchUserData 调用之后的下一行代码:console.log("程序继续执行...");。这也是一个同步任务,立刻执行。

    • 控制台输出: 程序继续执行... (这行代码会先于用户数据被打印)
  5. 第 5 步: 所有同步代码都执行完了。现在 JavaScript 引擎处于“空闲”状态,但它设置的那个 2 秒的定时器还在后台默默计时。

  6. 第 6 步 (2秒后): 定时器时间到!后台的“助手”把 setTimeout 的回调函数 () => { ... } 放进了任务队列。JavaScript 的事件循环 (Event Loop) 发现自己空闲,并且队列里有新任务,于是就把它拿出来执行。

  7. 第 7 步: 现在,setTimeout 内部的代码终于被执行了。

    • console.log("...网络请求耗时2秒..."); 被执行。
    • user 对象被创建。
    • callback(user) 被调用。这里的 callback 就是我们在第 2 步传入的那个函数。于是,user 对象被传递给了它。
    • 控制台输出:
      ...网络请求耗时2秒,现在返回数据...
      成功获取到用户数据!
      用户姓名: John Doe
      用户邮箱: john.doe@example.com
      

“回调地狱”的预兆

回调函数是解决异步问题的有效方式,但如果存在多个互相依赖的异步操作,事情就会变得棘手。

想象一下,你需要:

  1. 先获取用户信息。
  2. 然后根据用户信息获取他的文章列表。
  3. 再然后根据第一篇文章获取其所有评论。

使用回调函数,代码会变成这样:

fetchUserData(123, (user) => {console.log("拿到了用户信息:", user);fetchUserPosts(user.id, (posts) => {console.log("拿到了用户文章:", posts);fetchPostComments(posts[0].id, (comments) => {console.log("拿到了文章评论:", comments);// 如果还有下一步,就要继续往里嵌套...// ...});});
});

这种不断向右缩进的嵌套结构,就是臭名昭著的 “回调地狱” (Callback Hell)。它极难阅读、维护和进行错误处理。

为了解决“回调地狱”这个问题,JavaScript 引入了更优雅、更强大的异步处理方案:Promise。这将是我们下一个练习的核心。

你已经成功理解了异步和回调,这是非常重要的一步!准备好后,我们就去挑战 Promise,彻底告别回调地狱。

http://www.xdnf.cn/news/14791.html

相关文章:

  • 每天一个前端小知识 Day 17 - 微前端架构实战与 Module Federation
  • 基于Flask技术的民宿管理系统的设计与实现
  • 嵌入式原理与应用篇---常见基础知识(10)
  • 前后端Dockerfile
  • Spring生态:云原生与AI的革新突破
  • python环境快速搭建
  • Windows11系统中安装docker并配置docker镜像到pycharm中
  • 企业自建云概念解读|私有云、专有云、混合云、分布式云、企业云
  • 对称非对称加密,https和http,https通讯原理,Charles抓包原理
  • 《如何在 Spring 中实现 MQ 消息的自动重连:监听与发送双通道策略》
  • JavaEE初阶第五期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(三)
  • Windows环境下Docker容器化的安装与设置指南
  • 时序数据库IoTDB监控指标采集与可视化指南
  • 基于MATLAB的SVM支持向量机的乳腺癌分类方法应用
  • 现代 JavaScript (ES6+) 入门到实战(六):异步的终极形态 - async/await 的优雅魔法
  • HTTP中常见的Content-Type
  • HybridCLR热更新实例项目及改造流程
  • 现代 JavaScript (ES6+) 入门到实战(五):告别回调地狱,Promise 完全入门
  • 免费SSL证书一键申请与自动续期
  • STM32——HAL库总结
  • 【AGI】Qwen VLo:多模态AI的范式重构与AGI演进关键里程碑
  • mac触摸板设置右键
  • 【HuggingFace】模型下载至本地访问
  • 基于Pandas和FineBI的昆明职位数据分析与可视化实现(三)- 职位数据统计分析
  • 条件概率:不确定性决策的基石
  • C#写破解rar文件密码例程
  • 【硬核数学】10. “价值标尺”-损失函数:信息论如何设计深度学习的损失函数《从零构建机器学习、深度学习到LLM的数学认知》
  • Android大图加载优化:BitmapRegionDecoder深度解析与实战
  • IDE/IoT/实践小熊派LiteOS工程配置、编译、烧录、调试(基于 bearpi-iot_std_liteos 源码)
  • 马斯克的 Neuralink:当意念突破肉体的边界,未来已来