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