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