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

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.dtcms.com/a/262941.html

相关文章:

  • Karmada 多集群服务发现
  • Apache Doris Profile 深度解析:从获取到分析,解锁查询性能优化密码
  • RedhatCentos挂载镜像
  • LeetCode Hot100(图论)
  • SQL参数化查询:防注入与计划缓存的双重优势
  • 使用 Sqlcmd 高效导入大型 SQL Server 数据库脚本 (.sql)
  • 深入理解 B+ 树:数据库索引的脊梁
  • AI初学者如何对大模型进行微调?——零基础保姆级实战指南
  • vscode一个文件夹有残余的git仓库文件,已经失效了,怎样进行清空仓库残余文件并重新初始化git--ubuntu
  • 【stm32】HAL库开发——CubeMX配置RTC,单片机工作模式和看门狗
  • 炸鸡派-基础测试例程
  • Linux入门篇学习——Ubuntu 系统介绍和Ubuntu启用root用户
  • 在线五子棋对战项目
  • 1.1_2 计算机网络的组成和功能
  • python+uniapp基于微信小程序的食堂菜品查询系统
  • Deepoc 大模型:无人机行业的智能变革引擎
  • vue-33(实践练习:使用 Nuxt.js 和 SSR 构建一个简单的博客)
  • SpringCloud Gateway
  • C++ 第四阶段 STL 容器 - 第五讲:详解 std::set 与 std::unordered_set
  • 蓝牙耳机开发--探讨AI蓝牙耳机功能、瓶颈及未来展望
  • 链表题解——两两交换链表中的节点【LeetCode】
  • AWS 开源 Strands Agents SDK,简化 AI 代理开发流程
  • Objective-c把字符解析成字典
  • 【微服务】.Net中使用Consul实现服务高可用
  • 链表重排序问题
  • java JNDI高版本绕过 工具介绍 自动化bypass
  • Python训练营打卡Day58(2025.6.30)
  • 晨控CK-FR03与和利时LX系列PLC配置EtherNetIP通讯连接操作手册
  • linux下fabric环境搭建
  • [免费]微信小程序停车场预约管理系统(Springboot后端+Vue3管理端)【论文+源码+SQL脚本】