日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

JavaScript是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式!...

發布時間:2025/3/8 javascript 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaScript是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式!... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

此篇是 JavaScript是如何工作的第四篇,其它三篇可以看這里:

  • JavaScript是如何工作的:引擎,運行時和調用堆棧的概述!
  • JavaScript是如何工作的:深入V8引擎&編寫優化代碼的5個技巧!
  • JavaScript如何工作:內存管理+如何處理4個常見的內存泄漏!
  • 通過第一篇文章回顧在單線程環境中編程的缺陷以及如何解決這些缺陷來構建健壯的JavaScript UI。按照慣例,在本文的最后,分享5個如何使用async/ wait編寫更簡潔代碼的技巧。

    為什么單線程是一個限制?

    在發布的第一篇文章中,思考了這樣一個問題:當調用堆棧中有函數調用需要花費大量時間來處理時會發生什么?

    例如,假設在瀏覽器中運行一個復雜的圖像轉換算法。

    當調用堆棧有函數要執行時,瀏覽器不能做任何其他事情——它被阻塞了。這意味著瀏覽器不能渲染,不能運行任何其他代碼,只是卡住了。那么你的應用 UI 界面就卡住了,用戶體驗也就不那么好了。

    在某些情況下,這可能不是主要的問題。還有一個更大的問題是一旦你的瀏覽器開始處理調用堆棧中的太多任務,它可能會在很長一段時間內停止響應。這時,很多瀏覽器會拋出一個錯誤,提示是否終止頁面:

    JavaScript程序的構建塊

    你可能在單個.js文件中編寫 JavaScript 應用程序,但可以肯定的是,你的程序由幾個塊組成,其中只有一個正在執行,其余的將在稍后執行。最常見的塊單元是函數。

    大多數剛接觸JavaScript的開發人員似乎都有這樣的問題,就是認為所有函數都是同步完成,沒有考慮的異步的情況。如下例子:

    你可能知道標準 Ajax 請求不是同步完成的,這說明在代碼執行時 Ajax(..) 函數還沒有返回任何值來分配給變量 response。

    一種等待異步函數返回的結果簡單的方式就是 回調函數:

    注意:實際上可以設置同步Ajax請求,但永遠不要那樣做。如果設置同步Ajax請求,應用程序的界面將被阻塞——用戶將無法單擊、輸入數據、導航或滾動。這將阻止任何用戶交互,這是一種可怕的做法。

    以下是同步 Ajax 地,但是請千萬不要這樣做:

    這里使用Ajax請求作為示例,你可以讓任何代碼塊異步執行。

    這可以通過 setTimeout(callback,milliseconds) 函數來完成。setTimeout 函數的作用是設置一個回調函數milliseconds后執行,如下:

    function first() {console.log('first'); } function second() {console.log('second'); } function third() {console.log('third'); } first(); setTimeout(second, 1000); // Invoke `second` after 1000ms third();

    輸出:

    first third second

    解析事件循環

    這里從一個有點奇怪的聲明開始——盡管允許異步 JavaScript 代碼(就像上例討論的setTimeout),但在ES6之前,JavaScript本身實際上從來沒有任何內置異步的概念,JavaScript引擎在任何給定時刻只執行一個塊。

    那么,是誰告訴JS引擎執行程序的代碼塊呢?實際上,JS引擎并不是單獨運行的——它是在一個宿主環境中運行的,對于大多數開發人員來說,宿主環境就是典型的web瀏覽器或Node.js。實際上,現在JavaScript被嵌入到各種各樣的設備中,從機器人到燈泡,每個設備代表 JS 引擎的不同類型的托管環境。

    所有環境中的共同點是一個稱為事件循環的內置機制,它處理程序的多個塊在一段時間內通過調用調用JS引擎的執行。

    這意味著JS引擎只是任意JS代碼的按需執行環境,是宿主環境處理事件運行及結果。

    例如,當 JavaScript 程序發出 Ajax 請求從服務器獲取一些數據時,在函數(“回調”)中設置“response”代碼,JS引擎告訴宿主環境:"我現在要推遲執行,但當完成那個網絡請求時,會返回一些數據,請回調這個函數并給數據傳給它"。

    然后瀏覽器將偵聽來自網絡的響應,當監聽到網絡請求返回內容時,瀏覽器通過將回調函數插入事件循環來調度要執行的回調函數。以下是示意圖:

    這些Web api是什么?從本質上說,它們是無法訪問的線程,只能調用它們。它們是瀏覽器的并發部分。如果你是一個Nojs.jsjs開發者,這些就是 c++ 的 Api。

    這樣的迭代在事件循環中稱為(tick)標記,每個事件只是一個函數回調。

    讓我們“執行”這段代碼,看看會發生什么:

    1.初始化狀態都為空,瀏覽器控制臺是空的的,調用堆棧也是空的

    2. console.log('Hi')添加到調用堆棧中

    3. 執行console.log('Hi')

    4. console.log('Hi')從調用堆棧中移除。

    5. setTimeout(function cb1() { ... }) 添加到調用堆棧。

    6. setTimeout(function cb1() { ... }) 執行,瀏覽器創建一個計時器計時,這個作為Web api的一部分。

    7. setTimeout(function cb1() { ... })本身執行完成,并從調用堆棧中刪除。

    8. console.log('Bye') 添加到調用堆棧

    9. 執行 console.log('Bye')

    10. console.log('Bye') 從調用調用堆棧移除

    11. 至少在5秒之后,計時器完成并將cb1回調推到回調隊列。

    12. 事件循環從回調隊列中獲取cb1并將其推入調用堆棧。

    13. 執行cb1并將console.log('cb1')添加到調用堆棧。

    14. 執行 console.log('cb1')

    15. console.log('cb1') 從調用堆棧中移除

    16. cb1 從調用堆棧中移除

    快速回顧:

    值得注意的是,ES6指定了事件循環應該如何工作,這意味著在技術上它屬于JS引擎的職責范圍,不再僅僅扮演宿主環境的角色。這種變化的一個主要原因是ES6中引入了 Promises,因為ES6需要對事件循環隊列上的調度操作進行直接、細度的控制。

    setTimeout(…) 是怎么工作的

    需要注意的是,setTimeout(…)不會自動將回調放到事件循環隊列中。它設置了一個計時器。當計時器過期時,環境將回調放到事件循環中,以便將來某個標記(tick)將接收并執行它。請看下面的代碼:

    setTimeout(myCallback, 1000);

    這并不意味著myCallback將在1000毫秒后就立馬執行,而是在1000毫秒后,myCallback被添加到隊列中。但是,如果隊列有其他事件在前面添加回調剛必須等待前后的執行完后在執行myCallback。

    有不少的文章和教程上開始使用異步JavaScript代碼,建議用setTimeout(回調,0),現在你知道事件循環和setTimeout是如何工作的:調用setTimeout 0毫秒作為第二個參數只是推遲回調將它放到回調隊列中,直到調用堆棧是空的。

    請看下面的代碼:

    console.log('Hi'); setTimeout(function() {console.log('callback'); }, 0); console.log('Bye');

    雖然等待時間被設置為0 ms,但在瀏覽器控制臺的結果如下:

    Hi Bye callback

    ES6的任務隊列是什么?

    ES6中引入了一個名為“任務隊列”的概念。它是事件循環隊列上的一個層。最為常見在Promises 處理的異步方式。

    現在只討論這個概念,以便在討論帶有Promises的異步行為時,能夠了解 Promises 是如何調度和處理。

    想像一下:任務隊列是一個附加到事件循環隊列中每個標記末尾的隊列。某些異步操作可能發生在事件循環的一個標記期間,不會導致一個全新的事件被添加到事件循環隊列中,而是將一個項目(即任務)添加到當前標記的任務隊列的末尾。

    這意味著可以放心添加另一個功能以便稍后執行,它將在其他任何事情之前立即執行。

    任務還可能創建更多任務添加到同一隊列的末尾。理論上,任務“循環”(不斷添加其他任務的任等等)可以無限運行,從而使程序無法獲得轉移到下一個事件循環標記的必要資源。從概念上講,這類似于在代碼中表示長時間運行或無限循環(如while (true) ..)。

    任務有點像 setTimeout(callback, 0) “hack”,但其實現方式是引入一個定義更明確、更有保證的順序:稍后,但越快越好。

    回調

    正如你已經知道的,回調是到目前為止JavaScript程序中表達和管理異步最常見的方法。實際上,回調是JavaScript語言中最基本的異步模式。無數的JS程序,甚至是非常復雜的程序,除了一些基本都是在回調異步基礎上編寫的。

    然而回調方式還是有一些缺點,許多開發人員都在試圖找到更好的異步模式。但是,如果不了解底層的內容,就不可能有效地使用任何抽象出來的異步模式。

    在下一章中,我們將深入探討這些抽象,以說明為什么更復雜的異步模式(將在后續文章中討論)是必要的,甚至是值得推薦的。

    嵌套回調

    請看以下代碼:

    我們有一個由三個函數組成的鏈嵌套在一起,每個函數表示異步系列中的一個步驟。

    這種代碼通常被稱為“回調地獄”。但是“回調地獄”實際上與嵌套/縮進幾乎沒有任何關系,這是一個更深層次的問題。

    首先,我們等待“單擊”事件,然后等待計時器觸發,然后等待Ajax響應返回,此時可能會再次重復所有操作。

    乍一看,這段代碼似乎可以將其異步性自然地對應到以下順序步驟:

    listen('click', function (e) {// .. });

    然后:

    setTimeout(function(){// .. }, 500);

    接著:

    ajax('https://api.example.com/endpoint', function (text){// .. });

    最后:

    if (text == "hello") {doSomething(); } else if (text == "world") {doSomethingElse(); }

    因此,這種連續的方式來表示異步代碼似乎更自然,不是嗎?一定有這樣的方法,對吧?

    Promises

    請看下面的代碼:

    var x = 1; var y = 2; console.log(x + y);

    這非常簡單:它對x和y的值進行求和,并將其打印到控制臺。但是,如果x或y的值丟失了,仍然需要求值,要怎么辦?

    例如,需要從服務器取回x和y的值,然后才能在表達式中使用它們。假設我們有一個函數loadX和loadY`,它們分別從服務器加載x和y的值。然后,一旦x和y都被加載,假設我們有一個函數sum,它對x和y的值進行求和。

    它可能看起來像這樣(很丑,不是嗎?)

    這里有一些非常重要的事情——在這個代碼片段中,我們將x和y作為異步獲取的的值,并且執行了一個函數sum(…)(從外部),它不關心x或y,也不關心它們是否立即可用。

    當然,這種基于回調的粗略方法還有很多不足之處。 這只是一個我們不必判斷對于異步請求的值的處理方式一個小步驟而已。

    Promise Value

    用Promise來重寫上例:

    在這個代碼片段中有兩層Promise。

    fetchX 和 fetchY 先直接調用,返回一個promise,傳給 sum。 sum 創建并返回一個Promise,通過調用 then 等待 Promise,完成后,sum 已經準備好了(resolve),將會打印出來。

    第二層是 sum(…) 創建的 Promise ( 通過 Promise.all([ ... ]) )然后返回 Promise,通過調用then(…)來等待。當 sum(…) 操作完成時,sum 傳入的兩個 Promise 都執行完后,可以打印出來了。這里隱藏了在sum(…)中等待x和y未來值的邏輯。

    注意:在sum(...)內,Promise.all([...])調用創建一個 promise(等待 promiseX 和 promiseY 解析)。 然后鏈式調用 .then(...)方法里再的創建了另一個 Promise,然后把 返回的 x 和 和(values[0] + values[1]) 進行求和 并返回 。

    因此,我們在sum(...)末尾調用then(...)方法 ?—? 實際上是在返回的第二個 Pwwromise 上運行,而不是由Promise.all([ ... ])創建 Promise。 此外,雖然沒有在第二個 Promise 結束時再調用 then方法 ,其時這里也創建一個 Promise。

    Promise.then(…) 實際上可以使用兩個函數,第一個函數用于執行成功的操作,第二個函數用于處理失敗的操作:

    如果在獲取x或y時出現錯誤,或者在添加過程中出現某種失敗,sum(…) 返回的 Promise將被拒絕,傳遞給 then(…) 的第二個回調錯誤處理程序將從 Promise 接收失敗的信息。

    從外部看,由于 Promise 封裝了依賴于時間的狀態(等待底層值的完成或拒絕,Promise 本身是與時間無關的),它可以按照可預測的方式組成,不需要開發者關心時序或底層的結果。一旦 Promise 決議,此刻它就成為了外部不可變的值。

    可鏈接調用 Promise 真的很有用:

    創建一個延遲2000ms內完成的 Promise ,然后我們從第一個then(...)回調中返回,這會導致第二個then(...)等待 2000ms。

    注意:因為Promise 一旦被解析,它在外部是不可變的,所以現在可以安全地將該值傳遞給任何一方,因為它不能被意外地或惡意地修改,這一點在多方遵守承諾的決議時尤其正確。一方不可能影響另一方遵守承諾決議的能力,不變性聽起來像是一個學術話題,但它實際上是承諾設計最基本和最重要的方面之一,不應該被隨意忽略。

    使用 Promise 還是不用?

    關于 Promise 的一個重要細節是要確定某個值是否是一個實際的Promise 。換句話說,它是否具有像Promise 一樣行為?

    我們知道 Promise 是由new Promise(…)語法構造的,你可能認為` p instanceof Promise是一個足夠可以判斷的類型,嗯,不完全是。

    這主要是因為可以從另一個瀏覽器窗口(例如iframe)接收 Promise 值,而該窗口或框架具有自己的 Promise 值,與當前窗口或框架中的 Promise 值不同,所以該檢查將無法識別 Promise 實例。

    此外,庫或框架可以選擇性的封裝自己的 Promise,而不使用原生 ES6 的Promise 來實現。事實上,很可能在老瀏覽器的庫中沒有 Promise。

    吞掉錯誤或異常

    如果在 Promise 創建中,出現了一個javascript一場錯誤(TypeError 或者 ReferenceError),這個異常會被捕捉,并且使這個 promise 被拒絕。

    但是,如果在調用 then(…) 方法中出現了 JS 異常錯誤,那么會發生什么情況呢?即使它不會丟失,你可能會發現它們的處理方式有點令人吃驚,直到你挖得更深一點:

    看起來foo.bar()中的異常確實被吞噬了,不過,它不是。然而,還有一些更深層次的問題,我們沒有注意到。 p.then(…) 調用本身返回另一個 Promise,該 Promise 將被 TypeError 異常拒絕。

    處理未捕獲異常

    許多人會說,還有其他更好的方法。

    一個常見的建議是,Promise 應該添加一個 done(…),這實際上是將 Promise 鏈標記為 “done”。done(…) 不會創建并返回 Promise ,因此傳遞給 done(..) 的回調顯然不會將問題報告給不存在的鏈接 Promise 。

    Promise 對象的回調鏈,不管以 then 方法或 catch 方法結尾,要是最后一個方法拋出錯誤,都有可能無法捕捉到(因為 Promise 內部的錯誤不會冒泡到全局)。因此,我們可以提供一個 done 方法,總是處于回調鏈的尾端,保證拋出任何可能出現的錯誤。

    ES8中改進了什么 ?Async/await (異步/等待)

    JavaScript ES8引入了 async/await,這使得使用 Promise 的工作更容易。這里將簡要介紹async/await 提供的可能性以及如何利用它們編寫異步代碼。

    使用 async 聲明異步函數。這個函數返回一個 AsyncFunction 對象。AsyncFunction 對象表示該函數中包含的代碼的異步函數。

    調用使用 async 聲明函數時,它返回一個 Promise。當這個函數返回一個值時,這個值只是一個普通值而已,這個函數內部將自動創建一個承諾,并使用函數返回的值進行解析。當這個函數拋出異常時,Promise 將被拋出的值拒絕。

    使用 async 聲明函數時可以包含一個 await 符號,await 暫停這個函數的執行并等待傳遞的 Promise 的解析完成,然后恢復這個函數的執行并返回解析后的值。

    async/wait 的目的是簡化使用承諾的行為

    讓看看下面的例子:

    function getNumber1() {return Promise.resolve('374'); } // 這個函數與getNumber1相同 async function getNumber2() {return 374; }

    類似地,拋出異常的函數等價于返回被拒絕的 Promise 的函數:

    function f1() {return Promise.reject('Some error'); } async function f2() {throw 'Some error'; }

    await 關鍵字只能在異步函數中使用,并允許同步等待 Promise。如果在 async 函數之外使用 Promise,仍然需要使用 then 回調:

    還可以使用“異步函數表達式”定義異步函數。異步函數表達式與異步函數語句非常相似,語法也幾乎相同。異步函數表達式和異步函數語句之間的主要區別是函數名,可以在異步函數表達式中省略函數名來創建匿名函數。異步函數表達式可以用作生命(立即調用的函數表達式),一旦定義它就會運行。

    var loadData = async function() {// `rp` is a request-promise function.var promise1 = rp('https://api.example.com/endpoint1');var promise2 = rp('https://api.example.com/endpoint2');// Currently, both requests are fired, concurrently and// now we'll have to wait for them to finishvar response1 = await promise1;var response2 = await promise2;return response1 + ' ' + response2; }

    更重要的是,在所有主流的瀏覽器都支持 async/await:

    最后,重要的是不要盲目選擇編寫異步代碼的“最新”方法。理解異步 JavaScript 的內部結構非常重要,了解為什么異步JavaScript如此關鍵,并深入理解所選擇的方法的內部結構。與編程中的其他方法一樣,每種方法都有優點和缺點。

    編寫高度可維護性、非易碎異步代碼的5個技巧

    1、簡介代碼: 使用 async/await 可以編寫更少的代碼。 每次使用 async/await時,都會跳過一些不必要的步驟:使用.then,創建一個匿名函數來處理響應,例如:

    // rp是一個請求 Promise 函數。 rp(‘https://api.example.com/endpoint1').then(function(data) {// … });

    和:

    // `rp` is a request-promise function. var response = await rp(‘https://api.example.com/endpoint1');

    2、錯誤處理: Async/wait 可以使用相同的代碼結構(眾所周知的try/catch語句)處理同步和異步錯誤。看看它是如何與 Promise 結合的:

    function loadData() {try { // Catches synchronous errors.getJSON().then(function(response) {var parsed = JSON.parse(response);console.log(parsed);}).catch(function(e) { // Catches asynchronous errorsconsole.log(e); });} catch(e) {console.log(e);} }

    async function loadData() {try {var data = JSON.parse(await getJSON());console.log(data);} catch(e) {console.log(e);} }

    3、條件:用async/ wait編寫條件代碼要簡單得多:

    function loadData() {return getJSON().then(function(response) {if (response.needsAnotherRequest) {return makeAnotherRequest(response).then(function(anotherResponse) {console.log(anotherResponse)return anotherResponse})} else {console.log(response)return response}}) }

    async function loadData() {var response = await getJSON();if (response.needsAnotherRequest) {var anotherResponse = await makeAnotherRequest(response);console.log(anotherResponse)return anotherResponse} else {console.log(response);return response; } }

    4、堆棧幀:與 async/await不同,從 Promise 鏈返回的錯誤堆棧不提供錯誤發生在哪里。看看下面這些:

    function loadData() {return callAPromise().then(callback1).then(callback2).then(callback3).then(() => {throw new Error("boom");}) } loadData().catch(function(e) {console.log(err); // Error: boom at callAPromise.then.then.then.then (index.js:8:13) });

    與:

    async function loadData() {await callAPromise1()await callAPromise2()await callAPromise3()await callAPromise4()await callAPromise5()throw new Error("boom"); } loadData().catch(function(e) {console.log(err);// output// Error: boom at loadData (index.js:7:9) });

    5.調試:如果你使用過 Promise ,那么你知道調試它們是一場噩夢。例如,如果在一個程序中設置了一個斷點,然后阻塞并使用調試快捷方式(如“停止”),調試器將不會移動到下面,因為它只“逐步”執行同步代碼。使用async/wait,您可以逐步完成wait調用,就像它們是正常的同步函數一樣。

    編輯中可能存在的bug沒法實時知道,事后為了解決這些bug,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具Fundebug。

    原文:https://blog.sessionstack.com...

    你的點贊是我持續分享好東西的動力,歡迎點贊!

    一個笨笨的碼農,我的世界只能終身學習!

    更多內容請關注公眾號《大遷世界》!

    與50位技術專家面對面20年技術見證,附贈技術全景圖

    總結

    以上是生活随笔為你收集整理的JavaScript是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式!...的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 亚洲综合日韩精品欧美综合区 | 亚洲成a人片 | 中国美女洗澡免费看网站 | 日本v片| 99久久精品免费看国产四区 | www.rihan | 永久av在线 | 中文字幕日韩欧美一区二区 | 大尺度床戏揉捏胸视频 | 成人综合激情 | 少妇一区二区视频 | 欧美人与性动交α欧美片 | 涩涩爱在线 | 日韩成人精品在线观看 | 免费网站在线观看人数在哪动漫 | 亚洲制服一区二区 | 永久免费汤不热视频 | 奴性白洁会所调教 | 爆操巨乳美女 | 免费美女av | 毛片久久久久久 | 久久久久人妻一道无码AV | 天天狠狠操 | 国产在线不卡av | 在线播放黄色av | 久久婷综合 | 久久嫩 | 国产精品视频看看 | 桃谷绘里香在线观看 | 国产精品一区二区三区免费在线观看 | 国产在视频线精品视频 | 精品视频一区二区三区四区 | 久久99激情 | 日b视频免费观看 | 不卡av免费 | 91色影院| 欧美成人三级在线播放 | 伊人成人在线观看 | 欧美极品少妇xxxxⅹ喷水 | 国产小视频自拍 | 欧美日韩人妻精品一区在线 | 久久新视频 | 黄色一级片免费播放 | 欧美精产国品一二三区 | 日韩一级片网址 | 99精品偷自拍 | 日本成人三级电影 | jizz色| 中文字幕精品久久久久人妻红杏1 | 999久久久免费精品国产 | 国产不卡精品视频 | 亚洲va国产va天堂va久久 | 小萝莉末成年一区二区 | 欧美午夜网站 | 久久性生活片 | av片在线播放 | 国产毛片在线 | av中文字幕免费观看 | 伊人久久爱| 国产精品久久毛片av大全日韩 | 亚洲精品无 | 久久久久久久久一区 | 色干综合 | 成人短视频在线免费观看 | 狠狠操天天射 | av美女在线| 成人免费视频国产免费 | 国产乱国产| 亚洲精品喷潮一区二区三区 | 久久99精品久久久久子伦 | 日韩成人免费观看 | juliaann办公室丝袜大战 | 亚洲人成网址 | 福利在线视频观看 | 日批视频在线 | 亚洲精视频 | 欧美日韩69 | 一区二区三区精品免费视频 | 国产精品一区二区欧美 | 亚洲午夜18毛片在线看 | xx性欧美肥妇精品久久久久久 | 色狠狠一区二区三区 | 青青草在线免费 | 香蕉视频毛片 | 色婷婷综合视频 | 一区二区中文字幕 | 啪啪网视频| 岛国av大片| 91在线综合 | av最新版天堂资源在线 | 人人干人人看 | 91精品一区二区三区四区 | 精品欧美一区二区久久久 | 国产日韩网站 | 91 色| 日韩av一级片 | 精品孕妇一区二区三区 | 9l视频自拍蝌蚪9l视频 | 福利姬在线观看 |