JavaScript重難點突破:期約與異步函數

同步和異步

  1. ?同步(Synchronous)?
  • ?定義:任務按順序依次執行,前一個任務完成前,后續任務必須等待。

  • ?特點:阻塞性執行,程序邏輯直觀,但效率較低

  1. ?異步(Asynchronous)?
  • ?定義:任務發起后無需等待結果,程序繼續執行其他操作,待任務完成后通過回調或事件通知處理結果。

  • ?特點:非阻塞性執行,支持并發,資源利用率高

簡單來說,同步就是刷牙然后煮面,異步就是讓面在一邊煮著一邊跑去刷牙。

期約

期約是對尚不存在結果的一個替身。

在ES6中,期約是一種引用類型(Promise),使用new操作符實例化。

期約狀態機

期約對象有三種狀態,這些狀態是期約對象內置的,除了調用相應的API,否則不能對其進行更改。

  • pending(待定)

  • fulfilled(兌現)

  • rejected(拒絕)

新建一個期約對象,并且期約對象還沒進行任何操作時,期約對象的狀態為pending,當期約對象已經被成功解決后,則轉為fulfilled,而解決失敗則轉為rejected,具體讓期約狀態轉換的函數后面介紹。

待定(pending)是期約的最初始狀態。在待定狀態下,期約可以落定(settled)為代表成功的兌現(fulfilled)狀態,或者代表失敗的拒絕(rejected)狀態。
無論落定為哪種狀態都是不可逆的。只要從待定轉換為兌現或拒絕,期約的狀態就不再改變。而且,也不能保證期約必然會脫離待定狀態。因此,組織合理的代碼無論期約解決(resolve)
還是拒絕(reject),甚至永遠處于待定(pending)狀態,都應該具有恰當的行為。
重要的是,期約的狀態是私有的,不能直接通過JavaScript檢測到。這主要是為了避免根據讀取到的期約狀態,以同步方式處理期約對象。
另外,期約的狀態也不能被外部JavaScript代碼修改。這與不能讀取該狀態的原因一樣:【期約故意將異步行為封裝起來,從而隔離外部的同步代碼】

《JavaScript高級程序設計第四版》

如何理解期約對象

正如前面介紹的,期約對象是專門為異步編程而設計的,期約對象是對尚不存在結果的一個替身。

舉個例子,你參加了一場考試,試卷由他人進行批改(異步操作),而你在試卷批改的過程中是自由的,你可以吃飯睡覺,你也可以嘗試查詢試卷批改的狀態。

如果試卷還在批改當中,則返回pending,如果已經批改結束,并且你已經通過了考試,返回fulfilled,如果未通過考試,則返回rejected

理解了上述的場景,我們就能夠理解期約對象和期約對象的狀態機了。

  • 期約對象代表了某一個異步操作。

  • 期約狀態機代表了異步操作的完成狀態。

如何控制期約對象的狀態機

期約對象的狀態是私有的,只能通過期約對象內部的API進行操作。

向期約對象中傳入一個執行器函數(回調函數),期約對象會向執行器函數傳入兩個參數resolvereject,調用resolve()使期約狀態變為fulfilled,調用reject()使期約狀態變為rejected

    const waiter1 = new Promise((resolve, reject) => { });const waiter2 = new Promise((resolve, reject) => resolve());const waiter3 = new Promise((resolve, reject) => reject());// 其中undefined表示期約對象在完成后期待一個返回值,但這里沒有給返回值console.log('waiter1:', waiter1); // waiter1: Promise {<pending>}console.log('waiter2:', waiter2); // waiter2: Promise {<fulfilled>:undefined}console.log('waiter3:', waiter3); // waiter1: Promise {<rejected>:undefined}

請添加圖片描述

期約對象的靜態方法

  • Promise.resolve()

    這個方法可以直接創建一個fulfilled狀態的期約對象,這個期約對象的值由傳入的參數指定。

        const settled1 = Promise.resolve(3)const settled2 = Promise.resolve('我是字符串')const settled3 = Promise.resolve(new Promise(() => {}))console.log(settled1) // Promise {<fulfilled>: 3}console.log(settled2) // Promise {<fulfilled>: '我是字符串'}console.log(settled3) // Promise {<pending>}
    

    可以看到,我們傳入什么值,期約對象就會返回什么值。

    但是如果我們傳入的是另一個期約對象,則會直接返回傳入的期約對象。

  • Promise.reject()

    這個方法可以直接創建一個rejected狀態的期約對象,這個期約對象的值由傳入的參數指定。

        const settled1 = Promise.reject(3)const settled2 = Promise.reject('我是字符串')const settled3 = Promise.reject(new Promise(() => {}))console.log(settled1) // Promise {<rejected>: 3}console.log(settled2) // Promise {<rejected>: '我是字符串'}console.log(settled3) // Promise {<rejected>: Promise {<pending>}}
    

    這個方法和Promise.resolve()類似,也會將傳入的值作為期約對象的值返回。

    但不同的是,如果傳入一個期約對象,那么這個期約對象也會作為期約對象的值返回。

    調用reject()或者Promise.reject()都會拋出一個異步錯誤。同步代碼塊中的trycatch結構無法捕獲到異步錯誤,只有異步結構中才能捕獲異步錯誤

    期約的實例方法

  • 實現Thenable方法

    在ECMAScript暴露的異步結構中,任何對象都有一個then()方法。

  • Promise.prototype.then()

    then()方法掛載在Promise的原型上,所以被所有Promise實例對象共享。

    then()方法接收兩個回調函數參數,第一個參數在Promise對象的狀態落定為fulfilled時執行,第二個參數在Promise對象的狀態落定為rejected時執行。

    如何理解then()方法

    在平時寫js方法時,我們都是使用的同步代碼塊,也就是說,寫在后面的代碼一定后執行,比如我們在前面一行計算let sum = 10 + 1,那么我們就可以在這一行后面的任意位置輸出sum,因為sum的計算是寫在前面的,在同步代碼塊中,他已經被計算完畢了。

    現在我們使用了異步編程,我們已經知道Promise對象內置了一個狀態機,它用于通知自己是否執行完畢。

    由于Promise是異步執行的,假如我們在同步代碼塊中讀取Promise對象,我們有可能獲取3種結果。如果我們需要打印Promise的返回值,我們不可能在同步代碼塊中不停的檢測Promise的狀態,這樣會導致后面的代碼無法執行,這就違背了異步編程的初衷。

    所以我們使用then()方法,then()方法可以想象為一個觸發器,設置好then()方法后,只要Promise對象settled到了任意一種狀態,就會觸發then()方法中設定好的函數,這樣我們就可以異步的處理Promise的返回值而無需再在同步代碼塊中處理Promise的返回值了。

        const p1 = new Promise((resolve, reject) => {// 1秒后返回10 + 1的計算結果setTimeout(() => resolve(10 + 1), 1000);});p1.then(() => {console.log('我計算完成,被觸發了');console.log('我是then中的p1:',p1)}, () => { console.log('我計算失敗,被觸發了'); });console.log('我是同步代碼塊中的p1:',p1)
    

    請添加圖片描述

    可以看到,同步代碼塊由于執行的比較快,已經運行到輸出Promise對象的值了,但是此時Promise對象還沒執行完,狀態為pending,而then中卻可以正常輸出Promise的返回值11,這是因為then()中的第一個參數只有在Promise對象狀態為fulfilled時才被調用。

    我們將then()方法的第一個參數稱為onResolved處理程序,第二個參數稱為onRejected處理程序。

  • .then() 返回的 Promise 狀態如何確定?

    ?回調返回值類型決定狀態

    • ?返回普通值?(非 Promise 對象):新 Promise 會被 Promise.resolve() 包裝為 ?fulfilled 狀態

      p.then(() => 42); // 新 Promise 狀態:fulfilled,值:42
      
    • ?拋出異常:新 Promise 變為 ?rejected 狀態,異常對象作為拒絕原因

      p.then(() => { throw new Error("fail"); }); // 狀態:rejected,原因:Error對象
      
    • ?返回 Promise 對象:新 Promise 將 ?繼承該 Promise 的狀態和值

      p.then(() => Promise.reject("error")); // 新 Promise 狀態:rejected,原因:"error"
      

    因此通過.then()方法返回的也是Promise對象,所以也有.then()方法,.then()方法可以進行鏈式調用

        const p1 = new Promise((resolve, reject) => {// 3秒后返回10 + 1的計算結果setTimeout(() => resolve(double(1)), 1000);});p1.then(value => {return value}).then(value => {return double(value)}).then(value => {return double(value)}).then(value => console.log(value)) // 8
    
  • Promise.prototype.catch()

    等于then(null,() => {}),也就是onRejected處理程序。

  • Promise.prototype.finally()

    傳入finally()的回調函數能保證一定被執行,和try-catch-finally中的finally用法一致。

  • Promise.all()和Promise.race()

    這兩個方法都可以傳入一個包含多個期約的可迭代對象,常見方法是傳入一個包含多個期約的數組。

    • Promise.all():會等待傳入的期約全部兌現后才兌現,如果有一個期約待定或者拒絕,則返回待定或者拒絕

    • Promise.race():會返回根據第一個兌現或者拒絕的期約決定狀態的新期約對象。

異步函數

通過剛剛期約對象的學習我們了解到,期約對象是異步執行的,因此如果想操作期約對象的流程必須要使用then()方法。

但是這樣同樣導致了一個問題,同步代碼塊和異步代碼塊被完全的區分開了,從使用了期約對象開始,所有和這個期約對象有關的流程都要在then中實現,這會使得then()方法的函數體變得很大,并不好維護。

于是,從ES8開始引入了一組新的關鍵字async/await用于解決這個問題。

基本概念

  • ?async 函數

    • 聲明方式:在函數前添加 async 關鍵字,如 async function fetchData() {}

    • 返回值:始終返回一個 Promise 對象。若函數返回非 Promise 值(如字符串、數值),該值會被自動包裝為 resolve 狀態的 Promise

      async function example() { return "Hello"; }
      example().then(console.log); // 輸出 "Hello"
      
  • ?await 關鍵字

    • 使用范圍:僅能在 async 函數內部使用。

    • 功能:暫停當前 async 函數的執行,等待右側的 Promise 完成(resolvereject),并返回解析后的值

           async function fetchUser() {const response = await fetch('/api/user'); // 等待請求完成return response.json();
      }
      

    簡單來說,關鍵字async聲明了這個函數應該被異步執行,關鍵字await表明被async聲明的函數應該被停止執行,等到await右側的表達式返回值后才被繼續執行。

async

async關鍵字標記的函數會返回一個Promise對象,如果返回的值不是Promise對象,則會使用Promise.resolve()對返回的值進行包裝。

async 函數的執行流程

  1. ?同步代碼的立即執行
  • ?未遇到 awaitasync 函數內部的代碼會按照同步順序立即執行,與普通函數的行為完全一致。例如:

    async function demo() {console.log("A");  // 同步執行console.log("B");  // 同步執行
    }
    demo();
    console.log("C");
    

    輸出順序為:A → B → C

  • ?本質async 函數被調用時,其函數體內的同步代碼會直接進入主線程的同步任務隊列,立即執行。

  1. ?**await 對執行流程的干預**
  • ?遇到 await:函數會暫停當前執行,將 await 后的表達式(通常是 Promise)放入微任務隊列,并交出主線程控制權。此時,外部同步代碼會繼續執行。例如:

    async function demo() {console.log("A");await new Promise(resolve => setTimeout(resolve, 1000)); // 暫停console.log("B");  // 異步執行(微任務)
    }
    demo();
    console.log("C");
    

    輸出順序為:A → C → (1秒后) B

  • ?關鍵機制await 后的代碼會被封裝為微任務,等待當前同步代碼執行完畢后才會繼續執行。

    async function heavyTask () {console.log("開始耗時操作");// 沒有awaitfor (let i = 0; i < 1e9; i++);  console.log("耗時操作完成");}heavyTask();console.log("外部代碼");

請添加圖片描述

    async function heavyTask () {console.log("開始耗時操作");// 有awaitawait '123'for (let i = 0; i < 1e9; i++);  console.log("耗時操作完成");}heavyTask();console.log("外部代碼");

請添加圖片描述

可以看到,在await后的代碼才會作為異步代碼執行,否則async修飾的代碼會像普通函數一樣同步執行。

await

await關鍵字期待右側是一個實現了Thenable接口的對象。

但如果不是,則await不會等待,而是將右側視為一個已經fulfilled的期約對象,直接返回。不會將值包裝為Promise對象

    const func = async () => {console.log(await '123') }// 注意,123沒有被包裝為Promise對象func() // '123'

如果await右側是一個Promise對象,并且尚未settled,那么異步程序會在await處阻塞,停止運行直到右側的Promise對象已經fulfilled或者rejected

異步函數的特質不會擴展到嵌套函數,await只能在async標記的函數中使用

如果需要進行并行優化,不要每調用一次async函數就等待await返回值,而是一次性將async函數全部調用后,再按照需要的順序等待await的返回值。

    const asyncFunc1 = async () => { console.log(1); return 'a' }const asyncFunc2 = async () => { console.log(2); return 'b' }const asyncFunc3 = async () => { console.log(3); return 'c' }const asyncFunc4 = async () => { console.log(4); return 'd' }// 錯誤示范const run1 = async () => {console.log(await asyncFunc1()) console.log(await asyncFunc2()) console.log(await asyncFunc3()) console.log(await asyncFunc4()) }// 正確示范const run2 = async () => {const res1 = asyncFunc1()const res2 = asyncFunc2()const res3 = asyncFunc3()const res4 = asyncFunc4()console.log(await res1) console.log(await res2) console.log(await res3) console.log(await res4) }

在錯誤示范中,每次調用async函數都等到async函數返回后才執行下一個async函數。
而正確示范中,將所有async函數全部執行后,再等待async函數的返回值。

正確示范也可以通過Promise.all()來實現。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/75392.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/75392.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/75392.shtml

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

相關文章

學習總結 網格劃分+瞬態求解設置

網格劃分部分 1.導入幾何文件 導入我們的幾何模型&#xff0c;他的格式為.scdocx 2.添加局部尺寸BOI 因為要對對前緣和尾緣進行局部加密&#xff0c;所以進行一個BOI的局部加密&#xff0c;目標尺寸取的幾何尺寸的最小尺寸的0.1&#xff0c;就是0.4mm。 3.生成表面網格 表面…

.NET 使用 WMQ 連接Queue 發送 message 實例

1. 首先得下載客戶端&#xff0c;沒有客戶端無法發送message. 安裝好之后長這樣 我裝的是7.5 安裝目錄如下 tools/dotnet 目錄中有演示的demo 2. .Net 連接MQ必須引用bin目錄中的 amqmdnet.dll 因為他是創建Queuemanager 的核心庫&#xff0c; 項目中引用using IBM.WMQ; 才…

風電行業預測性維護解決方案:給風機裝上 “智能醫生”,實現故障 “秒級預警”

引言&#xff1a;風電設備故障為何成為 “運維黑洞”&#xff1f; 某海上風電場因齒輪箱軸承故障停機 3 天&#xff0c;直接損失 50 萬元發電量。傳統維護模式下&#xff0c;人工巡檢覆蓋率不足 40%&#xff0c;故障修復平均耗時 72 小時。而預測性維護通過物聯網 AI 技術&am…

5、無線通信基站的FPGA實現架構

基站&#xff08;Base Station&#xff0c;BS&#xff09;&#xff0c;也稱為公用移動通信基站&#xff0c;是無線電臺站的一種形式&#xff0c;具體則指在一定的無線電覆蓋區中&#xff0c;通過移動通信交換中心&#xff0c;與移動電話終端之間的信息傳遞的無線電收發信電臺。…

筆記2——網絡參考模型

一、OSI參考模型&#xff1a; 應用層&#xff1a; 報文 給應用程序提供接口 表示層&#xff1a; 進行數據格式的轉換 會話層&#xff1a; 在通訊雙方之間建立、管理和終止會話 傳輸層&#xff1a; 數據段&#xff1b;建立、維護、取消一次端到端的數據傳輸過程&#xff1b;控制…

最短路徑:Bellman-Ford算法

Bellman-Ford的操作步驟 1.初始化距離&#xff1a;將起點的dist值設置為0&#xff0c;其他點的dist值設置為無窮大。 2.執行n-1輪松弛操作&#xff1a;遍歷所有邊&#xff0c;更新最短距離&#xff0c;收斂后可獲得最短路徑。 3.檢測負權環&#xff1a;額外遍歷一次&#xf…

0402-對象和類(訪問器 更改器 日期類)

OOP&#xff1a;面向對象程序設計 類&#xff1a;構造對象的模板或藍圖 類構造對象的過程稱為創建類的實例 封裝&#xff1a;對外隱藏數據的真實實現方式&#xff0c;提供簡單的方法 &#xff08;類比方向盤&#xff09; 對象&#xff1a;本質上是內存中的一小塊空間 識別類&a…

【 <二> 丹方改良:Spring 時代的 JavaWeb】之 Spring Boot 中的文件上傳與下載:實現文件管理功能

<前文回顧> 點擊此處查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、開篇整…

搜索算法------DFS練習2

1. 題目 2. 思路和題解 從題目中可以看出&#xff0c;如果一個格子上有雨水&#xff0c;那么就可以流到周圍比他高度低的單元格&#xff0c;如果單元格和海洋相鄰&#xff0c;那么雨水也會流入海洋。總而言之一句話就是水從高處流向低處。從這里的流向可以聯想到深度優先搜索這…

[python] 正則表達式

1.分割str s"1-2--3---4" are.findall(r\d|[-],s) # 輸出&#xff1a;[1, -, 2, --, 3, ---, 4]s"-4(2(3)" # ? 表示 - 可以出現0次或1次 # \d 表示匹配一個或多個連續數字 # \D 表示匹配非數字字符 sre.findall(r-?\d|\D,s) # 輸出&#xff1a;[-4, (,…

定制化管理系統與通用管理系統,誰更勝一籌?

一、定制化管理系統與通用管理系統的定義與特點 定制化管理系統 定制化管理系統是根據企業的具體業務需求和流程進行個性化開發的軟件系統。它能夠深度貼合企業的管理需求&#xff0c;提供高度靈活的解決方案。其特點包括&#xff1a; 高度適應性&#xff1a;能夠精準匹配企業…

gitee 配置git上傳

Git入門&#xff1f;查看 幫助 , Visual Studio / TortoiseGit / Eclipse / Xcode 下如何連接本站, 如何導入倉庫 簡易的命令行入門教程: Git 全局設置: 以 176fuguM2項目為例 git config --global user.name "墮落圣甲蟲" git config --global user.email "11…

SpringBoot+Vue 中 WebSocket 的使用

WebSocket 是一種在單個 TCP 連接上進行全雙工通信的協議&#xff0c;它使得客戶端和服務器之間可以進行實時數據傳輸&#xff0c;打破了傳統 HTTP 協議請求 - 響應模式的限制。 下面我會展示在 SpringBoot Vue 中&#xff0c;使用WebSocket進行前后端通信。 后端 1、引入 j…

STM32 FATFS - 在SDIO的SD卡中運行fatfs

參考文章 STM32CubeMX | SD Card FATFS - 知乎 [STM32F4]基于F407的硬件移植Free RTOSFATFS&#xff08;SDIO&#xff09;_freertosfatfs-CSDN博客 例程地址&#xff1a;STM32FatFS: 基于stm32的fatfs例程&#xff0c;配合博客文章 基于梁山派天空星開發板&#xff0c;STM3…

Java 進化之路:從 Java 8 到 Java 21 的重要新特性

Java 進化之路&#xff1a;從 Java 8 到 Java 21 的重要新特性 開篇介紹 在軟件開發領域&#xff0c;Java 作為一門歷史悠久且廣泛應用的編程語言&#xff0c;始終保持著其核心競爭力和持續創新能力。自 Java 8 發布以來&#xff0c;Java 經歷了一系列重要版本更新&#xff0…

Reactor 事件流 vs. Spring 事件 (ApplicationEvent)

Reactor 事件流 vs. Spring 事件 ApplicationEvent Reactor 事件流 vs. Spring 事件 (ApplicationEvent)1?? 核心區別2?? Spring 事件 (ApplicationEvent)? 示例&#xff1a;Spring 事件發布 & 監聽1?? 定義事件2?? 發布事件3?? 監聽事件&#x1f539; 進階&…

JVM生產環境問題定位與解決實戰(六):總結篇——問題定位思路與工具選擇策略

本文已收錄于《JVM生產環境問題定位與解決實戰》專欄&#xff0c;完整系列見文末目錄 引言 在前五篇文章中&#xff0c;我們深入探討了JVM生產環境問題定位與解決的實戰技巧&#xff0c;從基礎的jps、jmap、jstat、jstack、jcmd等工具&#xff0c;到JConsole、VisualVM、MAT的…

【5090d】配置運行和微調大模型所需基礎環境【一】

RuntimeError: Failed to import transformers.integrations.bitsandbytes because of the following error (look up to see its traceback): No module named triton.ops 原因&#xff1a;是因為在導入 transformers.integrations.bitsandbytes 時缺少必要的依賴項 triton.op…

華為交換綜合實驗——VRRP、MSTP、Eth-trunk、NAT、DHCP等技術應用

一、實驗拓撲 二、實驗需求 1,內網Ip地址使用172.16.0.0/16分配 2,sw1和SW2之間互為備份 3, VRRP/STP/VLAN/Eth-trunk均使用 4,所有Pc均通過DHCP獲取IP地址 5,ISP只能配置IP地址 6,所有電腦可以正常訪問IsP路由器環回 三、需求分析 1、設備連接需求 二層交換機&#xff08;LS…

DeepSeek 開源的 3FS 如何?

DeepSeek 3FS&#xff08;Fire-Flyer File System&#xff09;是一款由深度求索&#xff08;DeepSeek&#xff09;于2025年2月28日開源的高性能并行文件系統&#xff0c;專為人工智能訓練和推理任務設計。以下從多個維度詳細解析其核心特性、技術架構、應用場景及行業影響&…