摘要:異步回調被作為實參傳入另一函數,并在該外部函數內被調用,用以來完成某些任務的函數,稱為回調函數。回調函數經常被用于繼續執行一個異步完成后的操作,它們被稱為異步回調。回調函數是事件循環回頭調用到程序中的目標,隊列處理到這個項目的時候會運行它。
唯一比不知道代碼為什么崩潰更可怕的事情是,不知道為什么一開始它是工作的!
在 ECMA 規范的最近幾次版本里不斷有新成員加入,尤其在處理異步的問題上,更是不斷推陳出新。然而,我們在享受便利的同時,也應該了解異步到底是怎么一回事。
現在與將來JavaScript 是單線程的,一次只能專注于一件事。如果瀏覽器只靠 JavaScript 引擎線程來完成所有工作,先不說能不能搞定,即使可以,那也會花費很長時間。幸好在瀏覽器里 JavaScript 引擎并不孤單,還有 GUI 渲染線程、事件觸發線程、定時觸發器線程、異步http請求線程等其它線程。這些線程之間的協作才有了我們看到的瀏覽器界面效果(遠不止這些)。
(盜了一張圖)
一個程序在執行過程中可能會有等待用戶輸入、從數據庫或文件系統中請求數據、通過網絡發送并等待響應,或是以固定時間間隔執行重復任務(比如動畫)等情況。(這些情況,當下是無法得出結果的,但是一旦有了結果,我們知道需要去做些什么。)
JavaScript 引擎不是一個人在戰斗,它把以上的任務交給其它線程,并計劃好任務完成后要做的事,JavaScript 引擎又可以繼續做自己的事了。從這里可以看出,一個程序的運行包括兩部分,現在運行和將來運行。而現在運行和將來運行的關系正是異步編程的核心。
let params = {type:"asynchronous"} let response = ajax(params,"http://someURL.com"); // 異步請求 if (!response) throw "無數據!";
以上代碼肯定會拋錯的,異步請求任務交出去之后,程序會繼續運行下去。由于ajax(...) 是異步操作,即使立刻返回結果,當下的 response 也不會被賦值。一個是現在,一個是將來,兩者本就不屬于一個時空的。
事件循環現在和將來是相對的,等將來的時刻到了,將來也就成為了現在。
JavaScript 引擎運行在宿主環境中,宿主環境提供了一種機制來處理程序中多個塊的執行,且執行每個塊時調用 JavaScript 引擎,這種機制被稱為事件循環。即,JavaScript 引擎本身并沒有時間的概念,只是一個按需執行 JavaScript 任意代碼片段的環境。
“事件”(JavaScript 代碼執行)調度總是由包含它的環境進行。
點擊圖片進入或點此進入:
一個 JavaScript 運行時包含了一個待處理的消息隊列。每一個消息都關聯著一個用以處理這個消息的函數。
在事件循環期間的某個時刻,運行時從最先進入隊列的消息開始處理隊列中的消息。為此,這個消息會被移出隊列,并作為輸入參數調用與之關聯的函數。
while (queue.waitForMessage()) { queue.processNextMessage(); }
一旦有事件需要進行,事件循環就會運行,直到隊列清空。事件循環的每一輪稱為一個 tick。用戶交互,IO 和定時器會向事件隊列中加入事件。
(又盜了一張圖)
任務隊列(job queue)建立在事件循環隊列之上。(Promise 的異步特性就是基于任務。)
最好的理解方式,它是掛在事件循環隊列的每個tick之后的一個隊列。在事件循環的每個tick中,可能出現的異步動作不會導致一個完整的新事件添加到事件循環隊列中,而會在當前 tick 的任務隊列末尾添加一個項目(一個任務)。
即,由 Call Stack 生成的任務隊列會緊隨其后運行。
Promise.resolve().then(function promise1 () { console.log("promise1"); }) setTimeout(function setTimeout1 (){ console.log("setTimeout1"); Promise.resolve().then(function promise2 () { console.log("promise2"); }) }, 0) setTimeout(function setTimeout2 (){ console.log("setTimeout2"); Promise.resolve().then(function promise3 () { console.log("promise3"); setTimeout(function setTimeout3 () { console.log("setTimeout3"); }) Promise.resolve().then(function promise4 () { console.log("promise4"); }) }) }, 0) // promise1 // setTimeout1 // promise2 // setTimeout2 // promise3 // promise4 // setTimeout3異步回調
被作為實參傳入另一函數,并在該外部函數內被調用,用以來完成某些任務的函數,稱為回調函數。回調函數經常被用于繼續執行一個異步完成后的操作,它們被稱為異步回調。立即執行的稱之為同步回調。
回調函數是事件循環“回頭調用”到程序中的目標,隊列處理到這個項目的時候會運行它。
回調是 JavaScript 語言中最基礎的異步模式。
生活中,我們喜歡和有條理的人打交道,因為我們的大腦習慣了這種思維模式。然而回調的使用打破了這種模式,因為代碼的嵌套使得我們要在不同塊間切換。嵌套越多,邏輯越復雜,我們也就越難理解和處理代碼,尤其在表達異步的方式上。
(又盜了一張圖)
除了嵌套的問題,異步回調還存在一些信任問題。
回調性質的不確定
調用回調方式不確定(沒調用,重復調用等)
......
針對第一點的建議是:永遠異步調用回調,即使就在事件循環的下一輪,這樣,所有回調都是可預測的異步調用了。
在理解這個建議之前,我們首先了解下控制反轉,控制反轉就是把自己程序一部分的執行控制交個某個第三方。
let a = 0; // A thirdparty(() => { console.log("a", a); // B }) a++; // C
A 和 C 是現在運行的,B 雖然代碼是我們的,但是卻受制于第三方,因為我們無法確定它是現在運行還是將來運行的。這里的回調函數可能是同步回調也可能是異步回調。a 是 0 還是 1,都有可能。
// 同步回調 const thirdparty = cb => { cb(); } // 異步回調 const thirdparty = cb => { setTimeout(() => cb(), 0); }
所以,永遠異步調用回調,可預測。
function asyncify(fn) { let func = fn; let t = setTimeout(() => { t = null; if (fn) fn(); }, 0); fn = null; return () => { if (t) { fn = func.bind(this, ...arguments); } else { func.apply(this, arguments); } } } let a = 0; thirdparty(asyncify(() => { console.log("a", a); })) a++; // 1
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101760.html
摘要:而事件循環是主線程中執行棧里的代碼執行完畢之后,才開始執行的。由此產生的異步事件執行會作為任務隊列掛在當前循環的末尾執行。在下,觀察者基于監聽事件的完成情況在下基于多線程創建。 主要問題: 1、JS引擎是單線程,如何完成事件循環的? 2、定時器函數為什么計時不準確? 3、回調與異步,有什么聯系和不同? 4、ES6的事件循環有什么變化?Node中呢? 5、異步控制有什么難點?有什么解決方...
摘要:調用棧被清空,消息隊列中并無任務,線程停止,事件循環結束。不確定的時間點請求返回,將設定好的回調函數放入消息隊列。調用棧執行完畢執行消息隊列任務。請求并發回調函數執行順序無法確定。 異步編程 JavaScript中異步編程問題可以說是基礎中的重點,也是比較難理解的地方。首先要弄懂的是什么叫異步? 我們的代碼在執行的時候是從上到下按順序執行,一段代碼執行了之后才會執行下一段代碼,這種方式...
摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:只要指定過這些事件的回調函數,這些事件發生時就會進入任務隊列,等待主線程讀取。異步任務必須指定回調函數,當主線程開始執行異步任務,就是執行對應的回調函數。 javascript語言是一門單線程的語言,不像java語言,類繼承Thread再來個thread.start就可以開辟一個線程。所以,javascript就像一條流水線,僅僅是一條流水線而已,要么加工,要么包裝,不能同時進行多個任...
摘要:異步那些事一基礎知識異步那些事二分布式事件異步那些事三異步那些事四異步那些事五異步腳本加載事件概念異步回調首先了講講中兩個方法和定義和用法方法用于在指定的毫秒數后調用函數或計算表達式。功能在事件循環的下一次循環中調用回調函數。 JS異步那些事 一 (基礎知識)JS異步那些事 二 (分布式事件)JS異步那些事 三 (Promise)JS異步那些事 四(HTML 5 Web Workers...
閱讀 3020·2021-11-24 10:32
閱讀 677·2021-11-24 10:19
閱讀 5068·2021-08-11 11:17
閱讀 1455·2019-08-26 13:31
閱讀 1259·2019-08-23 15:15
閱讀 2286·2019-08-23 14:46
閱讀 2264·2019-08-23 14:07
閱讀 1073·2019-08-23 14:03