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.pswp.cn/bicheng/87058.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/87058.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/87058.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Jenkins初探-通過Docker部署Jenkins并安裝插件

簡介 本文介紹了使用Docker安裝Jenkins并進行初始配置的完整流程。主要內容包括&#xff1a; (1)通過docker pull命令獲取Jenkins鏡像&#xff1b;(2)使用docker run命令啟動容器并映射端口&#xff1b;(3)訪問Jenkins界面獲取初始管理員密碼&#xff1b;(4)安裝推薦插件并創…

嵌入式開發:GPIO、UART、SPI、I2C 驅動開發詳解與實戰案例

&#x1f4cd; 本文為嵌入式學習系列第二篇&#xff0c;基于 GitHub 開源項目&#xff1a;0voice/EmbeddedSoftwareLearn &#x1f4ac; 作者&#xff1a;0voice &#x1f440; 適合對象&#xff1a;嵌入式初學者、STM32學習者、想搞明白外設驅動開發的C語言學習者 一、驅動是什…

常用 Linux 命令和 shell 腳本語言整理

目錄 一、Linux 命令大全 1、文件和目錄操作 &#xff08;1&#xff09;ls 列出目錄內容 &#xff08;2&#xff09;pwd 查看當前目錄 &#xff08;3&#xff09;cd 切換目錄 &#xff08;4&#xff09;mkdir 創建目錄 &#xff08;5&#xff09;cp 復制文件或目錄 &…

YOLOv12_ultralytics-8.3.145_2025_5_27部分代碼閱讀筆記-autobackend.py

autobackend.py ultralytics\nn\autobackend.py 目錄 autobackend.py 1.所需的庫和模塊 2.def check_class_names(names: Union[List, Dict]) -> Dict[int, str]: 3.def default_class_names(data: Optional[Union[str, Path]] None) -> Dict[int, str]: 4.cla…

【MySQL基礎】MySQL索引全面解析:從原理到實踐

MySQL學習&#xff1a; https://blog.csdn.net/2301_80220607/category_12971838.html?spm1001.2014.3001.5482 前言&#xff1a; 在前面我們基本上已經把MySQL的基礎知識都進行了學習&#xff0c;但是我們之前處理的數據都是十分少的&#xff0c;但是如果當我們的數據量很大…

第三十五章 I2S——音頻傳輸接口

第三十五章 I2S——音頻傳輸接口 目錄 第三十五章 I2S——音頻傳輸接口 1 I2S概述 1.1 簡介 1.2 功能特點 1.3 工作原理 1.4 利用DMA通信的I2S 1.4.1 I2S配合DMA通信工作原理 1.4.2 配置要點 2 應用場景 2.1 消費類音頻設備 2.2 專業音頻設備 2.3 通信設備 2.4 汽車電子 2.5 嵌…

產品-Figma(英文版),圖像的布爾類型圖例說明

文章目錄 Union SelectionSubtract SelectionIntersect SelectionExclude SelectionFlatten Selection Union Selection 把多個形狀合并成一個新的完整形狀&#xff0c;保留所有外部輪廓&#xff0c;內部不被切割。由于紅色的長方形在外面的一層&#xff0c;所以切割后&#x…

Windows CMD命令分類大全

?? ?一、系統與磁盤管理? ?系統信息? systeminfo&#xff1a;查看詳細硬件及系統配置&#xff08;版本/內存/補丁&#xff09;211 winver&#xff1a;快速檢查Windows版本11 msinfo32&#xff1a;圖形化系統信息面板811?磁盤工具? chkdsk /f&#xff1a;修復磁盤錯誤&…

【Dify系列】【Dify1.4.2 升級到Dify1.5.0】

1. 升級前準備工作 1.1 數據備份&#xff1a; 進入原安裝包 docker 目錄&#xff0c;備份“volumes”文件夾&#xff0c;此文件夾包含了 Dify 數據庫數據&#xff1a; rootjoe:/usr/local/dify/docker/volumes# pwd /usr/local/dify/docker/volumesrootjoe:/usr/local/dify/…

DeepSeek網頁版隨機點名器

用DeepSeek幫我們生成了一個基于html5的隨機點名器&#xff0c;效果非常棒&#xff0c;如果需要加入名字&#xff0c;請在代碼中按照對應的格式添加即可。 提示詞prompt 幫我生成一個隨機點名的HTML5頁面 生成真實一點的名字數據 點擊隨機按鈕開始隨機選擇 要有閃動的效果 &…

前后端分離實戰2----后端

戳我抵達前端 項目描述&#xff1a;用Vscode創建Spring Bootmybatis項目&#xff0c;用maven進行管理。創建一個User表&#xff0c;對其內容進行表的基本操作&#xff08;增刪改查&#xff09;&#xff0c;顯示在前端。 項目地址&#xff1a;戳我一鍵下載項目 運行效果如下&…

深入 ARM-Linux 的系統調用世界

1、引言 本篇文章以 ARM 架構為例&#xff0c;進行講解。需要讀者有一定的 ARM 架構基礎 在操作系統的世界中&#xff0c;系統調用&#xff08;System Call&#xff09;是用戶空間與內核空間溝通的橋梁。用戶態程序如 ls、cp 或你的 C 程序&#xff0c;無權直接操作硬件、訪問文…

LabVIEW鍵盤鼠標監測控制

通過Input Device Control VIs&#xff0c;實現對鍵盤和鼠標活動的監測。通過AcquireInput Data VI 在循環中持續獲取輸入數據&#xff0c;InitializeKeyboard與InitializeMouse VIs 先獲取設備ID 引用&#xff0c;用于循環內監測操作&#xff1b;運行時可輸出按鍵信息&#xf…

Linux 系統管理:自動化運維與容器化部署

在現代 IT 基礎設施中&#xff0c;自動化運維和容器化部署是提高系統管理效率和可維護性的關鍵。Linux 系統因其穩定性和靈活性而被廣泛應用于服務器和數據中心。本文將深入探討 Linux 系統管理中的自動化運維和容器化部署技術&#xff0c;幫助系統管理員實現高效運維和快速部署…

直播 APP 開發需要多少成本

直播行業的火爆催生了大量直播 APP 開發需求&#xff0c;而開發成本是開發者最關注的問題之一。其成本構成復雜&#xff0c;受功能需求、開發方式、技術難度等多種因素影響。? 基礎功能開發是成本的重要組成部分。用戶注冊登錄、直播間創建與管理、視頻播放、聊天互動等功能開…

Reactor操作符的共享與復用

在 Reactor 中&#xff0c;transform 和 transformDeferred 是兩個用于代碼復用和操作符鏈封裝的高級操作符。它們允許你將一組操作符封裝成一個函數&#xff0c;并在適當的時候應用到響應式流中。以下是它們的詳細總結&#xff1a; 1. transform 操作符 作用&#xff1a;tran…

C#中的Converter詳解

Converter是C#中一個非常有用的概念&#xff0c;主要用于類型轉換。它通常以委托或接口的形式出現&#xff0c;允許開發者定義如何將一種類型轉換為另一種類型。下面我將詳細介紹Converter的概念、使用場景&#xff0c;并以布爾型轉換為例展示具體應用。 Converter的基本概念 …

LabVIEW熒光微管圖像模擬

利用LabVIEW平臺&#xff0c;集成 PI 壓電平臺、Nikon 熒光顯微鏡及Andor sCMOS 相機等硬件&#xff0c;構建熒光微管滑行實驗圖像序列模擬系統。通過程序化模擬微管運動軌跡、熒光標記分布及顯微成像過程&#xff0c;為生物醫學領域微管跟蹤算法測試、運動特性分析提供標準化仿…

CentOS下Nginx服務器搭建全攻略

Nginx 安裝與配置完整指南 一、安裝 Nginx 1.1 添加 Nginx 官方倉庫 在 CentOS 系統中&#xff0c;默認倉庫的 Nginx 版本可能較舊&#xff08;通常為 1.12 或更早版本&#xff09;&#xff0c;建議添加官方倉庫來安裝最新穩定版本&#xff08;目前為 1.25.x&#xff09;&am…

網絡拓撲圖繪制全流程:從架構解析到工具實戰

在數據呈現與系統管理中&#xff0c;清晰展示設備間的邏輯關系至關重要。網絡拓撲圖正是這樣一種有效的可視化工具。它通過節點設備和連接線路&#xff0c;直觀呈現網絡結構或項目流程中各元素的布局與交互關系&#xff0c;幫助理解系統運作、診斷問題并確保項目順利進行。 1. …