摘要:而異步則是相反,調(diào)用在發(fā)出之后,這個調(diào)用就直接返回了,所以沒有返回結(jié)果而是在調(diào)用發(fā)出后,被調(diào)用者通過狀態(tài)通知來通知調(diào)用者,或通過回調(diào)函數(shù)處理這個調(diào)用。總結(jié)回調(diào)函數(shù)是異步編程中的基石,但同時也存在很多問題,不太適合人類自然語言的線性思維習慣。
為什么 JS 是單線程?
眾所周知,Javascript 語言的執(zhí)行環(huán)境是"單線程"(single thread)。
所謂"單線程",就是指一次只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執(zhí)行后面一個任務,以此類推。
而瀏覽器是多線程的,JS 線程就是其中一個:
瀏覽器 GUI 渲染線程
JavaScript 引擎線程
瀏覽器定時觸發(fā)器線程
瀏覽器事件觸發(fā)線程
瀏覽器 http 異步請求線程
瀏覽器線程知識中重要的一點是:
GUI渲染進程和 JavaScript 引擎進程是互斥的,因為如果這兩個線程可以同時運行的話, JavaScript 的 DOM 操作將會擾亂渲染線程執(zhí)行渲染前后的數(shù)據(jù)一致性。而且如果 DOM 一變化,界面就立刻重新渲染,效率必然很低
所以 JS 主線程執(zhí)行任務時,瀏覽器渲染線程處于掛起狀態(tài)。
同理,如果 JS 采用多線程同步的模型,那么如何保證同一時間修改了 DOM, 到底是哪個線程先生效呢?從操作系統(tǒng)調(diào)度多線程的上下文開銷,到實際編程里的鎖、線程同步等問題,都讓開發(fā)變得比較困難。
所以 JS 最終采用了單線程的事件模型。
我之前的文章《JS專題之事件循環(huán)》也有講過這塊內(nèi)容,歡迎翻閱。
一、同步與異步單線程模式這種排隊執(zhí)行的好處是實現(xiàn)起來比較簡單,執(zhí)行環(huán)境相對單純;壞處是只要有一個任務耗時很長,后面的任務都必須排隊等著,會拖延整個程序的執(zhí)行。常見的瀏覽器無響應(假死),往往就是因為某一段Javascript代碼長時間運行(比如死循環(huán)),導致整個頁面卡在這個地方,其他任務無法執(zhí)行。
為了解決這個問題,Javascript語言將任務的執(zhí)行模式分成兩種:同步(Synchronous)和異步(Asynchronous)。
那同步和異步的區(qū)別是什么?
我們想象一個很常見的場景:我們?nèi)ッ骛^吃牛肉面,柜臺人很多,前面在排隊下單。
這個時候,同步就是,收銀員收了你的錢,告訴你要在柜臺站著等面煮好,煮好后,就端面開吃,后面的人也只能等前面的人面煮好了才能付款下單然后等著面煮好端走~
而異步就是,收銀員收了你的錢,然后給了你一張小票,小票上有一個你的編號,收銀員告訴你,可以去座位上,你的面一煮好,會大聲叫你,你就來端面開吃。
我們可以看出,我們是過程的調(diào)用者,面館是被調(diào)用者,牛肉面煮好,是我們想要的結(jié)果,同步是調(diào)用者需要主動地等待這個結(jié)果。異步是被動的等待結(jié)果,當被調(diào)用者有結(jié)果了,就會通過消息機制或者回調(diào)機制告訴調(diào)用者結(jié)果。
同步和異步關注的是消息通信機制,同步就是在發(fā)出一個調(diào)用時,在沒有得到結(jié)果之前,該調(diào)用就不返回。但是一旦調(diào)用返回,就得到返回值了。而異步則是相反,調(diào)用在發(fā)出之后,這個調(diào)用就直接返回了,所以沒有返回結(jié)果, 而是在調(diào)用發(fā)出后,被調(diào)用者通過狀態(tài)、通知來通知調(diào)用者,或通過回調(diào)函數(shù)處理這個調(diào)用。
以上:
下單吃面是發(fā)起調(diào)用函數(shù)
端面開吃的回調(diào)函數(shù)
煮好的面是調(diào)用的結(jié)果,也是回調(diào)函數(shù)的參數(shù)
將例子抽象成偽代碼:
orderNoodle("牛肉面", function(noodle) { // 端面 getNoodle(); // 吃面 eatNoodle(); });三、事件循環(huán)
關于事件循環(huán)如何執(zhí)行異步代碼可以翻閱前面的文章《JS專題之事件循環(huán)》,這里大概提一下。
如果遇到異步事件,JS 引擎會把事件函數(shù)壓入執(zhí)行調(diào)用棧,但瀏覽器識別到它是異步事件后,會將其彈出執(zhí)行棧,當異步函數(shù)有返回結(jié)果后,JS 引擎將異步事件的回調(diào)函數(shù)放入事件隊列中,如果執(zhí)行調(diào)用棧為空,就將回調(diào)函數(shù)壓入執(zhí)行調(diào)用棧執(zhí)行。
四、回調(diào)函數(shù)在 JavaScript 中,函數(shù) function 作為一等公民,使用上非常自由,無論調(diào)用它,或者作為參數(shù),或者作為返回值都可以。
因為單線程異步的特點,后來在 JS 中,慢慢將函數(shù)的業(yè)務重點轉(zhuǎn)移到了回調(diào)函數(shù)中。
function step1(cb) { console.log("step1"); cb() } function step2(){ console.log("step2"); } step1(step2); // step1 step2
代碼會按先后順序執(zhí)行 step1, step2。
現(xiàn)在假設我們有這樣的需求:請求文件1后,獲取文件1 中的數(shù)據(jù)后請求文件2,獲取文件 2 中的數(shù)據(jù)后,又請求文件三。
var fs = require("fs"); fs.readFile("./file1.json", function(err, data1) { fs.readFile("./file2.json", function (err, data2) { fs.readFile("./file3.json", function(err, data3) { }) }) })五、回調(diào)函數(shù)的問題
由第四節(jié)可以看出,回調(diào)函數(shù)的寫法存在很多問題。
回調(diào)地獄(洋蔥模型)
當多個異步事務多級依賴時,回調(diào)函數(shù)會形成多級的嵌套,被花括號一層層包括,代碼變成
金字塔型結(jié)構(gòu),也被稱為回調(diào)地獄和洋蔥模型。
在回調(diào)地獄的情況下,代碼邏輯的梳理,流程的控制,代碼封裝維護,錯誤處理都變得越來越困難。
異常處理
try...catch 是被設計成捕獲當前執(zhí)行環(huán)境的異常,意思是只能捕獲同步代碼里面的異常,異步調(diào)用里面的異常無法捕獲。
function readFile(fileName) { setTimeout(function () { throw new Error("類型錯誤"); }, 1000); } try { readFile("./file1.json"); } catch (e) { // 如果異步事件出錯,打印不出來錯誤信息 console.log("err", e); }
在 nodejs 對回調(diào)函數(shù)采用 error first 的思想,回調(diào)函數(shù)的第一個參數(shù)保留給一個錯誤error對象,如果有錯誤發(fā)生,錯誤將通過第一個參數(shù)err返回。
原因是一個有回調(diào)函數(shù)的函數(shù),執(zhí)行分兩段,第一段執(zhí)行完之后,任務所在的上下文環(huán)境就已經(jīng)結(jié)束了。在這以后拋出的錯誤,原來的上下文已經(jīng)無法捕捉,只能當做參數(shù),傳入第二階段。
fs.readFile("/etc/passwd", "utf8", function (err, data) { if(err) { console.log(err) return; } });總結(jié)
回調(diào)函數(shù)是 JS 異步編程中的基石,但同時也存在很多問題,不太適合人類自然語言的線性思維習慣。
接下來幾篇文章,我將梳理 JS 中異步編程中的歷史演進中 Promise, generator, async&await 相關的內(nèi)容,歡迎關注。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/101762.html
摘要:因為瀏覽器環(huán)境里是單線程的,所以異步編程在前端領域尤為重要。除此之外,它還有兩個特性,使它可以作為異步編程的完整解決方案函數(shù)體內(nèi)外的數(shù)據(jù)交換和錯誤處理機制。 showImg(https://segmentfault.com/img/bVz9Cy); 在我們?nèi)粘>幋a中,需要異步的場景很多,比如讀取文件內(nèi)容、獲取遠程數(shù)據(jù)、發(fā)送數(shù)據(jù)到服務端等。因為瀏覽器環(huán)境里Javascript是單線程的,...
摘要:傳統(tǒng)的異步方法回調(diào)函數(shù)事件監(jiān)聽發(fā)布訂閱之前寫過一篇關于的文章,里邊寫過關于異步的一些概念。內(nèi)部函數(shù)就是的回調(diào)函數(shù),函數(shù)首先把函數(shù)的指針指向函數(shù)的下一步方法,如果沒有,就把函數(shù)傳給函數(shù)屬性,否則直接退出。 Generator函數(shù)與異步編程 因為js是單線程語言,所以需要異步編程的存在,要不效率太低會卡死。 傳統(tǒng)的異步方法 回調(diào)函數(shù) 事件監(jiān)聽 發(fā)布/訂閱 Promise 之前寫過一篇關...
javascript -- 回調(diào)函數(shù) 在高級語言層出不窮的年代, 各個語言都號稱有著一切皆為對象的自豪說法, 而 js 作為一門腳本語言卻相對于java等傳統(tǒng)面向?qū)ο笳Z言有很大的不同之處, 除了 js 詭異的繼承體系之外, 最令人著迷的一個特性就是回調(diào)函數(shù), 當然也有很多人對他詬病, 筆者認為 回調(diào)函數(shù) 和 異步 是js語言特性的兩大最為突出的店, 當然正如所有優(yōu)點需要滿足自我的需求, 這個世界...
摘要:異步編程是編寫的一個很重要的理念,特別是在處理復雜應用的時候,異步編程的技巧就至關重要。那么下面就來看看這個被稱為里程碑式的異步編程庫吧。 1. 前言 最近在看司徒正美的《JavaScript框架設計》,看到異步編程的那一章介紹了jsdeferred這個庫,覺得很有意思,花了幾天的時間研究了一下代碼,在此做一下分享。 異步編程是編寫js的一個很重要的理念,特別是在處理復雜應用的時候,異...
摘要:不少第三方模塊并沒有做到異步調(diào)用,卻裝作支持回調(diào),堆棧的風險就更大。我們可以編寫一個高階函數(shù),讓傳入的函數(shù)順序執(zhí)行還是我們之前的例子看起來還是很不錯的,簡潔并且清晰,最終的代碼量也沒有增加。 原文: http://pij.robinqu.me/JavaScript_Core/Functional_JavaScript/Async_Programing_In_JavaScript....
閱讀 3700·2023-04-26 02:55
閱讀 2926·2021-11-02 14:38
閱讀 4230·2021-10-21 09:39
閱讀 2923·2021-09-27 13:36
閱讀 4065·2021-09-22 15:08
閱讀 2747·2021-09-08 10:42
閱讀 2875·2019-08-29 12:21
閱讀 753·2019-08-29 11:22