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

js代码04

题目

非常好。我们刚刚看到了回调函数在处理多个异步操作时会变得多么混乱(回调地狱)。为了解决这个问题,现代 JavaScript 提供了一个更强大、更优雅的工具:Promise

Promise,正如其名,是一个“承诺”。它代表一个尚未完成但最终会完成(或失败)的异步操作的结果。你可以把它想象成一张“凭证”,你拿着这张凭证,未来可以凭它兑换最终的数据,或者得到一个失败的通知。


练习 04: Promise - 告别回调地狱

这次,我们重构上一个练习的 fetchUserData 函数。它不再接收回调函数作为参数,而是返回一个 Promise

🎯 学习目标:

  • 理解 Promise 的概念和它的三种状态:pending (进行中)、fulfilled (已成功)、rejected (已失败)。
  • 学会使用 new Promise((resolve, reject) => { ... }) 来创建一个 Promise 对象,并封装一个异步操作。
  • 学会使用 resolve 函数来表示承诺成功兑现,并传递结果。
  • 学会使用 reject 函数来表示承诺未能兑现,并传递原因。
  • 学会使用 .then() 方法来处理成功的结果,使用 .catch() 方法来捕获和处理错误。

🛠️ 任务:

  1. 重新实现 fetchUserData 函数,它现在只接收一个参数 userId
  2. 这个函数必须返回一个 new Promise 对象。
  3. 在 Promise 的构造函数内部(我们称之为 “executor”),执行我们的异步逻辑:
    • 错误处理: 首先检查 userId。如果它是一个小于或等于 0 的无效值,应立刻调用 reject 函数,并传入一个错误消息字符串,例如 "Invalid User ID"
    • 异步模拟: 使用 setTimeout 模拟一个 2 秒的网络延迟。
    • 成功处理: 在 2 秒钟后,setTimeout 的回调函数应该调用 resolve 函数,并将模拟的用户数据对象作为参数传给它。
  4. 观察并理解如何使用 .then().catch() 来消费这个返回 Promise 的函数。

📋 初始代码:
创建新文件 04-promises.js,并复制以下代码。你的任务是补全 fetchUserData 函数的内部逻辑。

console.log("程序开始...");/*** 使用 Promise 模拟从服务器获取用户数据。* @param {number} userId - 要获取的用户的ID。* @returns {Promise<object>} 一个 Promise 对象,成功时会 resolve 用户对象,失败时会 reject 错误信息。*/
function fetchUserData(userId) {return new Promise((resolve, reject) => {// --- 在这里编写你的代码 ---// 1. 检查 userId 是否有效,如果无效,调用 reject。// 2. 使用 setTimeout 模拟网络延迟。// 3. 在 setTimeout 的回调中,调用 resolve 并传入用户数据。});
}// --- 如何使用这个返回 Promise 的新函数 ---// 1. 模拟一次成功的调用
console.log("发起第一次调用 (userId: 123)...");
fetchUserData(123).then((user) => {// 当 Promise 成功时 (resolve被调用),这部分代码会执行console.log("成功获取到用户(123):", user);}).catch((error) => {// 当 Promise 失败时 (reject被调用),这部分代码会执行console.error("获取用户(123)失败:", error);});// 2. 模拟一次失败的调用
console.log("发起第二次调用 (userId: -1)...");
fetchUserData(-1).then((user) => {console.log("成功获取到用户(-1):", user);}).catch((error) => {console.error("获取用户(-1)失败:", error);});console.log("Promise 已发出,代码继续执行,等待结果...");

✅ 预期输出:
观察输出的顺序非常重要!

程序开始...
发起第一次调用 (userId: 123)...
发起第二次调用 (userId: -1)...
Promise 已发出,代码继续执行,等待结果...
获取用户(-1)失败: Invalid User ID  // 这条会很快出现,因为它没有进入setTimeout
// ...等待大约2秒...
成功获取到用户(123): { id: 123, name: 'John Doe', email: 'john.doe@example.com' } // 这条在2秒后出现

这个练习将清晰地展示 Promise 如何将“结果”与“处理结果的逻辑”分离开来,并提供了统一的错误处理机制,这正是它比回调函数更优秀的地方。开始吧!

答案

当然,我们来揭晓答案,并深入解析 Promise 的工作机制。这是异步编程从入门到熟练的关键一步。

参考实现 (04-promises.js)

console.log("程序开始...");/*** 使用 Promise 模拟从服务器获取用户数据。* @param {number} userId - 要获取的用户的ID。* @returns {Promise<object>} 一个 Promise 对象,成功时会 resolve 用户对象,失败时会 reject 错误信息。*/
function fetchUserData(userId) {// 返回一个新的 Promise 实例return new Promise((resolve, reject) => {// 1. 检查 userId 是否有效。这是同步代码,会立刻执行。if (userId <= 0) {// 如果无效,我们立刻调用 reject 来表示 Promise 失败。// Promise 的状态从 pending 变为 rejected。reject("Invalid User ID");return; // 调用 reject 后最好 return,以防止后续代码意外执行。}// 2. 使用 setTimeout 模拟异步操作setTimeout(() => {// 这部分代码会在 2 秒后执行// 模拟成功获取数据const user = {id: userId,name: 'John Doe',email: 'john.doe@example.com'};// 调用 resolve 表示 Promise 成功完成。// Promise 的状态从 pending 变为 fulfilled。// user 对象会作为成功的结果被传递出去。resolve(user);}, 2000);});
}// --- 如何使用这个返回 Promise 的新函数 ---// 1. 模拟一次成功的调用
console.log("发起第一次调用 (userId: 123)...");
fetchUserData(123).then((user) => {console.log("成功获取到用户(123):", user);}).catch((error) => {console.error("获取用户(123)失败:", error);});// 2. 模拟一次失败的调用
console.log("发起第二次调用 (userId: -1)...");
fetchUserData(-1).then((user) => {console.log("成功获取到用户(-1):", user);}).catch((error) => {console.error("获取用户(-1)失败:", error);});console.log("Promise 已发出,代码继续执行,等待结果...");

代码解析:Promise 的生命周期

让我们分别追踪“成功”和“失败”这两次调用的完整旅程。

A. 成功的旅程 (fetchUserData(123))
  1. 创建: 调用 fetchUserData(123),一个新的 Promise 对象被立刻创建并返回。此时,它的内部状态是 pending (进行中)。
  2. 执行: Promise 构造函数里的代码开始执行。userId (123) > 0,所以 if 判断不成立。
  3. 等待: setTimeout 被设置。程序继续向下执行,打印出 “Promise 已发出…”。主线程现在空闲了,等待 2 秒。
  4. 成功 (Fulfill): 2 秒后,setTimeout 的回调触发。user 对象被创建,然后 resolve(user) 被调用
  5. 状态变更: resolve() 的调用,使 Promise 的状态从 pending 变为 fulfilled (已成功)user 对象作为成功的结果被“封装”起来。
  6. 触发 .then: Promise 状态的改变会自动触发链接在它上面的 .then() 方法。user 对象被作为参数传递给 .then() 内部的那个箭头函数。
  7. 结果: 控制台打印出 成功获取到用户(123): ...
B. 失败的旅程 (fetchUserData(-1))
  1. 创建: 调用 fetchUserData(-1),另一个新的 pending 状态的 Promise 对象被创建并返回。
  2. 执行: Promise 构造函数里的代码开始执行。userId (-1) <= 0,if 判断成立
  3. 失败 (Reject): reject("Invalid User ID") 被立刻调用
  4. 状态变更: reject() 的调用,使 Promise 的状态从 pending 变为 rejected (已失败)。错误消息 "Invalid User ID" 作为失败的原因被“封装”起来。
  5. 触发 .catch: Promise 状态的改变会自动触发链接在它上面的 .catch() 方法(它会跳过所有 .then)。错误消息被作为参数传递给 .catch() 内部的箭头函数。
  6. 结果: 控制台打印出 获取用户(-1)失败: Invalid User ID。因为这个过程是同步的(没有 setTimeout),所以这个结果会很快出现。

Promise 解决了什么?

现在我们可以回答,为什么 Promise 比回调函数好。

  1. 统一的接口: 无论成功还是失败,你都与同一个 Promise 对象打交道,使用 .then().catch() 这两个标准方法来处理,代码结构清晰。

  2. 避免回调地狱: Promise 的核心优势在于链式调用 (Chaining)。如果我们需要在获取用户后,再获取他的文章,代码会是这样:

    fetchUserData(123).then(user => {// user 获取成功,现在发起获取文章的请求// 假设 fetchUserPosts 也返回一个 Promisereturn fetchUserPosts(user.id); }).then(posts => {// posts 获取成功,现在可以处理文章了console.log("用户的文章:", posts);}).catch(error => {// 任何一步失败,都会被这一个 .catch 捕获!console.error("在处理过程中发生错误:", error);});
    

    看到了吗?没有层层嵌套,而是一个扁平、线性的链条,非常易于阅读和维护。一个 .catch 就能处理整个链条中任何环节的错误,这是巨大的优势。

虽然 Promise 已经非常强大,但 ES2017 (ES8) 引入了 async/await 语法,它让我们能以一种更像同步代码的、更直观的方式来使用 Promise。这被称为异步编程的“终极形态”,也是我们下一个练习的主题。

准备好后,告诉我,我们就可以开始学习 async/await 了。

http://www.lqws.cn/news/579781.html

相关文章:

  • 生信分析之流式数据分析:Flowjo 软件核心功能全解析
  • 《微信生态裂变增长利器:推客小程序架构设计与商业落地》
  • 颠覆传统加密:微算法科技创新LSQb算法,提升量子图像处理速度
  • python | numpy小记(四):理解 NumPy 中的 `np.round`:银行家舍入策略
  • BUUCTF在线评测-练习场-WebCTF习题[MRCTF2020]你传你[特殊字符]呢1-flag获取、解析
  • 攻防世界-MISC-red_green
  • 障碍感知 | 基于3D激光雷达的三维膨胀栅格地图构建(附ROS C++仿真)
  • macos 使用 vllm 启动模型
  • 【数据分析】环境数据降维与聚类分析教程:从PCA到可视化
  • OpenCV CUDA模块设备层----计算向量的平方根函数sqrt
  • 【机器人】复现 HOV-SG 机器人导航 | 分层 开放词汇 | 3D 场景图
  • 极海G32R501双向数字电源解决方案 赋能AI服务器及电源应用创新
  • Android中Compose常用组件以及布局使用方法
  • 深入解析TCP:可靠传输的核心机制与实现逻辑
  • 首次使用“非英伟达”芯片!OpenAI租用谷歌TPU,降低推理计算成本
  • 成像光谱遥感技术中的AI革命:ChatGPT在遥感领域中的应用
  • (LeetCode 每日一题) 594. 最长和谐子序列 (哈希表)
  • redis相关内容以及安全知识
  • 开疆智能CCLinkIE转Canopen网关连接UV紫外灯配置案例
  • python包管理工具uv VS pip
  • iOS 接口频繁请求导致流量激增?抓包分析定位与修复全流程
  • 人工智能和云计算对金融未来的影响
  • 力扣 hot100 Day30
  • 键盘第一下无反应
  • Armbian 25.5.1 Noble Gnome 开启远程桌面功能
  • CMake中WIN32和CMAKE_HOST_WIN32的使用差异
  • Pytest pytest_runtest_makereport 钩子函数:测试失败信息收集与处理 —— Python 实践
  • (5)pytest-yield操作
  • Python量化金融:从数据到策略的工程实现
  • Serverless 架构入门与实战:AWS Lambda、Azure Functions、Cloudflare Workers 对比