JavaScript異步編程

同步與異步模式

js最初是設(shè)計(jì)使用在瀏覽器上的腳本語言镰踏,由于需要對DOM進(jìn)行操作念链,因此是單線程的執(zhí)行語言。

同步模式

  • 非同步執(zhí)行而是排隊(duì)執(zhí)行贝润;
  • 變量或函數(shù)的聲明不會產(chǎn)生任何的調(diào)用绊茧;
  • js在執(zhí)行引擎當(dāng)中維護(hù)了一個正在工作(執(zhí)行)的工作表,里面記錄當(dāng)前的執(zhí)行任務(wù)打掘,當(dāng)工作表中所有的任務(wù)被清空华畏,這一輪的工作結(jié)束;
  • 排隊(duì)執(zhí)行會存在如果遇到耗時多的任務(wù)尊蚁,那么后面的任務(wù)就會被延遲執(zhí)行->阻塞亡笑。

異步模式

// 異步舉例
console.log('global begin')

setTimeout(function timer1() {
    console.log('timer1 invoked')
}, 1800)

setTimeout(function timer2() {
    console.log('timer2 invoked')

    setTimeout(function inner() {
        console.log('inner invoked')
    }, 1000);
}, 1000);

console.log('global end')

// global begin
// global end
// timer2 invoked
// timer1 invoked
// inner invoked
  • 如果沒有異步模式,單線程的js無法同時處理大量的耗時任務(wù)横朋;
  • 難點(diǎn):代碼的執(zhí)行順序混亂仑乌;
  • 下達(dá)這個任務(wù)開啟的指令然后繼續(xù)往下執(zhí)行,不會等待任務(wù)結(jié)束琴锭。

js實(shí)現(xiàn)異步編程的4種方法:
4種解決方式的根本都是利用了瀏覽器定時器的工作原理晰甚。

  1. 回調(diào)函數(shù)
    異步編程最基本的方法。
    優(yōu)點(diǎn):簡單易理解决帖。
    缺點(diǎn):不利于代碼閱讀和維護(hù)厕九,各部分之間高度耦合,流程會很混亂地回,而且每個任務(wù)只能指定一個回調(diào)函數(shù)扁远。

  2. 事件監(jiān)聽
    采用事件驅(qū)動模式腺阳,任務(wù)執(zhí)行不取決于代碼的執(zhí)行順序,而是某個事件是否發(fā)生穿香。
    優(yōu)點(diǎn):易理解亭引,可以綁定多個事件,每個事件可以綁定多個回調(diào)函數(shù)皮获,能夠“去耦合”焙蚓,有利于實(shí)現(xiàn)模塊化。
    缺點(diǎn):整個程序變成事件驅(qū)動型洒宝,運(yùn)行流程不清晰购公。

  3. 發(fā)布/訂閱

假設(shè)存在一個“信號中心”,某個任務(wù)執(zhí)行完成雁歌,就向信號中心“發(fā)布(publish)”一個信號宏浩,其他任務(wù)可以向信號中心“訂閱(subscribe)”這個信號,從而知道自己什么時候開始執(zhí)行任務(wù)靠瞎,這就是“發(fā)布/訂閱模式”比庄,也稱“觀察者模式”。

這種方法的性質(zhì)與事件監(jiān)聽類似乏盐,但是可以通過“信號中心”查看佳窑,了解有多少信號、每個信號有多少訂閱者父能,從而監(jiān)控程序的運(yùn)行神凑。

  1. Promise對象

回調(diào)函數(shù)

由調(diào)用者定義,交給執(zhí)行者執(zhí)行的函數(shù)何吝。

事件循環(huán)與消息隊(duì)列

js引擎線程會維護(hù)一個執(zhí)行棧(調(diào)用棧call stack)溉委,同步代碼會依次加入執(zhí)行棧并執(zhí)行,結(jié)束會退出執(zhí)行棧爱榕。
js引擎線程如果遇到異步(DOM事件監(jiān)聽瓣喊、網(wǎng)絡(luò)請求、setTimeout計(jì)時器等)呆细,會交給單獨(dú)的線程(??Web APIs)維護(hù)異步任務(wù)型宝,直到滿足一定條件(用戶點(diǎn)擊DOM、網(wǎng)絡(luò)請求成功絮爷、計(jì)時器結(jié)束),由事件觸發(fā)線程將異步對應(yīng)的回調(diào)函數(shù)封裝成任務(wù)并加入消息隊(duì)列梨树。
如果執(zhí)行棧為坑夯,事件循環(huán)就會啟動,從消息隊(duì)列中取出一個任務(wù)(即異步的回調(diào)函數(shù))放入執(zhí)行棧中執(zhí)行抡四。

  • 事件循環(huán)

在線程運(yùn)行過程中柜蜈,接收并執(zhí)行新的任務(wù)仗谆,

  • 消息隊(duì)列

消息隊(duì)列是一種數(shù)據(jù)結(jié)構(gòu),可以存放要執(zhí)行的任務(wù)淑履,類似于待辦事件列表隶垮。

異步編程的幾種方式

Promise異步方案、宏任務(wù)/微任務(wù)隊(duì)列

Promise異步方案

Promise對象用于表示一個異步操作的最終完成(或失斆卦搿)及其結(jié)果值狸吞。

Promise有三個狀態(tài):
1.pending([待定]初始狀態(tài))
2.fulfilled([實(shí)現(xiàn)]操作成功)
3.rejected([被否決]操作失敗)

當(dāng)Promise狀態(tài)發(fā)生改變指煎,就會觸發(fā).then()里的響應(yīng)函數(shù)處理后續(xù)步驟蹋偏,由于.then().catch()方法返回的是一個新的Promise對象,因此它們可以被鏈?zhǔn)秸{(diào)用至壤。

  • Promise基本用法
// Promise 基本實(shí)例
const promise = new Promise((resolve, reject) => {
    // 這里用于“兌現(xiàn)”承諾
    resolve('100')  // 承諾達(dá)成
    reject(new Error('promise rejected!'))  // 承諾失敗
})

promise.then((value) => {
    console.log('resolved', value)
}, (error) => {
    console.log('rejected', error)
})
  • Promise使用案例
// Promise方式的AJAX
function ajax(url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest()
        xhr.open('GET', url)
        xhr.responseType = 'json'
        xhr.onload = function () {
            if (this.status === 200) {
                resolve(this.response)
            } else {
                reject(this.statusText)
            }
        }
        xhr.send()
    })
}
  • Promise常見誤區(qū)與鏈?zhǔn)秸{(diào)用
    嵌套使用的方式是使用Promise最常見的錯誤威始,應(yīng)該經(jīng)盡可能保證異步任務(wù)的扁平化。
    鏈?zhǔn)秸{(diào)用:
    每一個.then()方法實(shí)際上都是為上一個.then()返回的Promise對象添加狀態(tài)明確過后的回調(diào)像街。通過鏈?zhǔn)秸{(diào)用避免回調(diào)的嵌套黎棠。

  • Promise異常處理
    最好使用catch明確捕獲每一個異常。

  • Promise靜態(tài)方法

    • Promise.resolve():快速地把一個值轉(zhuǎn)換成Promise對象镰绎;如果包裹一個Promise對象葫掉,那么該P(yáng)romise對象會被原樣返回;還可以傳入一個有then方法的Promise對象跟狱,一般用于將第三方庫的Promise對象轉(zhuǎn)換為原生的Promise對象
    • Promise.reject():快速地創(chuàng)建一個失敗的Promise對象
  • Promise并行執(zhí)行
    同步執(zhí)行多個Promise的方式:

    • Promise.all():接收一個包含Promise對象的數(shù)組俭厚,將其中的Promise對象看作一個個異步任務(wù),返回一個全新的Promise對象驶臊,等待所有的任務(wù)結(jié)束
    • Promise.race():只會等待第一個任務(wù)結(jié)束
宏任務(wù)/微任務(wù)隊(duì)列

回調(diào)隊(duì)列中的任務(wù)稱之為「宏任務(wù)」

事件循環(huán)作為任務(wù)驅(qū)動的主線程挪挤,首先執(zhí)行完調(diào)用棧上當(dāng)前的宏任務(wù)(同步任務(wù)),然后再遍歷微任務(wù)隊(duì)列关翎,把微任務(wù)隊(duì)列上所有任務(wù)都執(zhí)行完畢(清空微任務(wù)隊(duì)列)(微任務(wù)也可以往微任務(wù)隊(duì)列中添加微任務(wù))扛门,接著渲染線程,最后從宏任務(wù)隊(duì)列中取一個任務(wù)纵寝,進(jìn)入下一個消息循環(huán)论寨。

宏任務(wù)執(zhí)行過程中可以臨時加上一些額外需求,對于額外需求可以選擇作為一個新的宏任務(wù)進(jìn)到隊(duì)列中排隊(duì)爽茴,也可以作為當(dāng)前任務(wù)的「微任務(wù)」葬凳,直接在當(dāng)前任務(wù)結(jié)束過后立即執(zhí)行,而非到隊(duì)伍末尾重新排隊(duì)室奏。
Promise的回調(diào)會作為微任務(wù)執(zhí)行火焰,setTimeout以宏任務(wù)的形式進(jìn)入隊(duì)列末尾。
微任務(wù)的提出是為了提高整體的響應(yīng)能力胧沫。
目前絕大多是異步調(diào)用都是作為宏任務(wù)執(zhí)行昌简,Promise&MutationObserver占业、process.nextTick會作為微任務(wù)執(zhí)行。

  • 產(chǎn)生宏任務(wù)的方式

    • script中的代碼塊
    • setTimeout()
    • setInterval()
    • setImmediate()(非標(biāo)準(zhǔn)纯赎、IE和Node.js中支持)
    • 注冊事件
  • 產(chǎn)生微任務(wù)的方式

    • Promise
    • MutationObserver
    • queueMicrotask()
  • 何時使用微任務(wù)
    微任務(wù)執(zhí)行的時機(jī)谦疾,晚于當(dāng)前本輪事件循環(huán)的Call Stack(調(diào)用棧)中的代碼(宏任務(wù)),早于時間處理函數(shù)和定時函數(shù)犬金。
    使用微任務(wù)的最主要原因簡單歸納為:
    1.減少操作中用戶可感知到的延遲(微任務(wù)中操作dom之后立即渲染)念恍;
    2.確保任務(wù)順序的一致性,即便是結(jié)果或數(shù)據(jù)是同步可用的佑附;
    3.批量操作的優(yōu)化樊诺。

Generator異步方案、Async/Await語法糖

Generator異步方案

Generator函數(shù)是一個封裝的異步任務(wù)音同,或者說是異步任務(wù)的容器词爬。

  • generator由function *定義,不同于普通函數(shù)权均,可以暫停執(zhí)行顿膨;
  • 異步操作需要暫停的地方,用yield語句注明叽赊;
  • 調(diào)用next()執(zhí)行g(shù)enerator函數(shù)恋沃,從上次返回的yield語句處繼續(xù)執(zhí)行。
Async/Await語法糖

語言層面的異步編程標(biāo)準(zhǔn)必指。async函數(shù)返回一個Promise對象囊咏,await等待接收async函數(shù)的返回值。是Generator的語法糖塔橡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末梅割,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子葛家,更是在濱河造成了極大的恐慌户辞,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件癞谒,死亡現(xiàn)場離奇詭異底燎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)弹砚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門双仍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人迅栅,你說我怎么就攤上這事殊校。” “怎么了读存?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵为流,是天一觀的道長。 經(jīng)常有香客問我让簿,道長敬察,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任尔当,我火速辦了婚禮莲祸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘椭迎。我一直安慰自己锐帜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布畜号。 她就那樣靜靜地躺著缴阎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪简软。 梳的紋絲不亂的頭發(fā)上蛮拔,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音痹升,去河邊找鬼建炫。 笑死,一個胖子當(dāng)著我的面吹牛疼蛾,可吹牛的內(nèi)容都是我干的肛跌。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼察郁,長吁一口氣:“原來是場噩夢啊……” “哼衍慎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绳锅,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤西饵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鳞芙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眷柔,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年原朝,在試婚紗的時候發(fā)現(xiàn)自己被綠了驯嘱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡喳坠,死狀恐怖鞠评,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情壕鹉,我是刑警寧澤剃幌,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布聋涨,位于F島的核電站,受9級特大地震影響负乡,放射性物質(zhì)發(fā)生泄漏牍白。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一抖棘、第九天 我趴在偏房一處隱蔽的房頂上張望茂腥。 院中可真熱鬧,春花似錦切省、人聲如沸最岗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽般渡。三九已至,卻和暖如春右蹦,著一層夾襖步出監(jiān)牢的瞬間诊杆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工何陆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晨汹,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓贷盲,卻偏偏與公主長得像淘这,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子巩剖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 還記得一年前寫過一篇關(guān)于JavaScript異步編程簡述的文章铝穷,主要介紹了JavaScript的單線程特性與異步編...
    極樂君閱讀 388評論 1 7
  • 同步模式與異步模式 事件循環(huán)與消息隊(duì)列 ??JavaScript 單線程指的是瀏覽器中負(fù)責(zé)解釋和執(zhí)行 JavaSc...
    Dear丶BQ閱讀 356評論 0 0
  • Event Loop 可以看出: Promise和setTimeout都是是異步 Promise優(yōu)先級高于setT...
    夏末遠(yuǎn)歌閱讀 297評論 0 0
  • 所謂"異步",簡單說就是一個任務(wù)分成兩段佳魔,先執(zhí)行第一段曙聂,然后轉(zhuǎn)而執(zhí)行其他任務(wù),當(dāng)?shù)谝欢斡辛藞?zhí)行結(jié)果之后鞠鲜,再回過頭執(zhí)...
    DJL簫氏閱讀 428評論 0 2
  • 同步和異步 同步編程宁脊,就是計(jì)算機(jī)一行一行按照順序依次執(zhí)行代碼,當(dāng)前代碼任務(wù)耗時執(zhí)行會阻塞后續(xù)代碼的執(zhí)行贤姆。 同步編輯...
    vuturn閱讀 270評論 0 0