亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JavaScript引擎實現(xiàn)async/await的方法實例

 更新時間:2022年03月30日 09:45:53   作者:Aleo亮亮  
大家應(yīng)該都知道隨著Node 7的發(fā)布,越來越多的人開始研究據(jù)說是異步編程終級解決方案的async/await,下面這篇文章主要給大家介紹了關(guān)于JavaScript引擎是如何實現(xiàn)async/await的相關(guān)資料,需要的朋友可以參考下

前言

我們都知道Promise 能很好地解決回調(diào)地獄的問題,但是這種方式充滿了 Promise 的 then() 方法,如果處理流程比較復(fù)雜的話,那么整段代碼將充斥著 then,語義化不明顯,代碼不能很好地表示執(zhí)行流程,使用 promise.then 也是相當(dāng)復(fù)雜,雖然整個請求流程已經(jīng)線性化了,但是代碼里面包含了大量的 then 函數(shù),使得代碼依然不是太容易閱讀?;谶@個原因,ES7 引入了 async/await,這是 JavaScript 異步編程的一個重大改進,提供了在不阻塞主線程的情況下使用同步代碼實現(xiàn)異步訪問資源的能力,并且使得代碼邏輯更加清晰。

JavaScript 引擎是如何實現(xiàn) async/await 的。如果上來直接介紹 async/await 的使用方式的話,那么你可能會有點懵,所以我們就從其最底層的技術(shù)點一步步往上講解,從而帶你徹底弄清楚 async 和 await 到底是怎么工作的。

首先介紹生成器(Generator)是如何工作的,接著講解 Generator 的底層實現(xiàn)機制——協(xié)程(Coroutine);又因為 async/await 使用了 Generator 和 Promise 兩種技術(shù),所以緊接著我們就通過 Generator 和 Promise 來分析 async/await 到底是如何以同步的方式來編寫異步代碼的。

生成器 VS 協(xié)程

生成器函數(shù)是一個帶星號函數(shù),而且是可以暫停執(zhí)行和恢復(fù)執(zhí)行的。

function* genDemo() {
    console.log("開始執(zhí)行第一段")
    yield 'generator 2'

    console.log("開始執(zhí)行第二段")
    yield 'generator 2'

    console.log("開始執(zhí)行第三段")
    yield 'generator 2'

    console.log("執(zhí)行結(jié)束")
    return 'generator 2'
}

console.log('main 0')
let gen = genDemo()
console.log(gen.next().value)
console.log('main 1')
console.log(gen.next().value)
console.log('main 2')
console.log(gen.next().value)
console.log('main 3')
console.log(gen.next().value)
console.log('main 4')

執(zhí)行上面這段代碼,觀察輸出結(jié)果,你會發(fā)現(xiàn)函數(shù) genDemo 并不是一次執(zhí)行完的,全局代碼和 genDemo 函數(shù)交替執(zhí)行。其實這就是生成器函數(shù)的特性,可以暫停執(zhí)行,也可以恢復(fù)執(zhí)行。下面我們就來看看生成器函數(shù)的具體使用方式:

  • 在生成器函數(shù)內(nèi)部執(zhí)行一段代碼,如果遇到 yield 關(guān)鍵字,那么 JavaScript 引擎將返回關(guān)鍵字后面的內(nèi)容給外部,并暫停該函數(shù)的執(zhí)行。
  • 外部函數(shù)可以通過 next 方法恢復(fù)函數(shù)的執(zhí)行。

關(guān)于函數(shù)的暫停和恢復(fù),相信你一定很好奇這其中的原理,那么接下來我們就來簡單介紹下 JavaScript 引擎 V8 是如何實現(xiàn)一個函數(shù)的暫停和恢復(fù)的,這也會有助于你理解后面要介紹的 async/await。

要搞懂函數(shù)為何能暫停和恢復(fù),那你首先要了解協(xié)程的概念。協(xié)程是一種比線程更加輕量級的存在。你可以把協(xié)程看成是跑在線程上的任務(wù),一個線程上可以存在多個協(xié)程,但是在線程上同時只能執(zhí)行一個協(xié)程,比如當(dāng)前執(zhí)行的是 A 協(xié)程,要啟動 B 協(xié)程,那么 A 協(xié)程就需要將主線程的控制權(quán)交給 B 協(xié)程,這就體現(xiàn)在 A 協(xié)程暫停執(zhí)行,B 協(xié)程恢復(fù)執(zhí)行;同樣,也可以從 B 協(xié)程中啟動 A 協(xié)程。通常,如果從 A 協(xié)程啟動 B 協(xié)程,我們就把 A 協(xié)程稱為 B 協(xié)程的父協(xié)程。

正如一個進程可以擁有多個線程一樣,一個線程也可以擁有多個協(xié)程。最重要的是,協(xié)程不是被操作系統(tǒng)內(nèi)核所管理,而完全是由程序所控制(也就是在用戶態(tài)執(zhí)行)。這樣帶來的好處就是性能得到了很大的提升,不會像線程切換那樣消耗資源。

為了讓你更好地理解協(xié)程是怎么執(zhí)行的,我結(jié)合上面那段代碼的執(zhí)行過程,畫出了下面的“協(xié)程執(zhí)行流程圖”,你可以對照著代碼來分析:

從圖中可以看出來協(xié)程的四點規(guī)則:

  • 通過調(diào)用生成器函數(shù) genDemo 來創(chuàng)建一個協(xié)程 gen,創(chuàng)建之后,gen 協(xié)程并沒有立即執(zhí)行。
  • 要讓 gen 協(xié)程執(zhí)行,需要通過調(diào)用 gen.next。
  • 當(dāng)協(xié)程正在執(zhí)行的時候,可以通過 yield 關(guān)鍵字來暫停 gen 協(xié)程的執(zhí)行,并返回主要信息給父協(xié)程。
  • 如果協(xié)程在執(zhí)行期間,遇到了 return 關(guān)鍵字,那么 JavaScript 引擎會結(jié)束當(dāng)前協(xié)程,并將 return 后面的內(nèi)容返回給父協(xié)程。

不過,對于上面這段代碼,你可能又有這樣疑問:父協(xié)程有自己的調(diào)用棧,gen 協(xié)程時也有自己的調(diào)用棧,當(dāng) gen 協(xié)程通過 yield 把控制權(quán)交給父協(xié)程時,V8 是如何切換到父協(xié)程的調(diào)用棧?當(dāng)父協(xié)程通過 gen.next 恢復(fù) gen 協(xié)程時,又是如何切換 gen 協(xié)程的調(diào)用棧?

要搞清楚上面的問題,你需要關(guān)注以下兩點內(nèi)容。

第一點:gen 協(xié)程和父協(xié)程是在主線程上交互執(zhí)行的,并不是并發(fā)執(zhí)行的,它們之前的切換是通過 yield 和 gen.next 來配合完成的。

第二點:當(dāng)在 gen 協(xié)程中調(diào)用了 yield 方法時,JavaScript 引擎會保存 gen 協(xié)程當(dāng)前的調(diào)用棧信息,并恢復(fù)父協(xié)程的調(diào)用棧信息。同樣,當(dāng)在父協(xié)程中執(zhí)行 gen.next 時,JavaScript 引擎會保存父協(xié)程的調(diào)用棧信息,并恢復(fù) gen 協(xié)程的調(diào)用棧信息。

為了直觀理解父協(xié)程和 gen 協(xié)程是如何切換調(diào)用棧的

到這里相信你已經(jīng)弄清楚了協(xié)程是怎么工作的,其實在 JavaScript 中,生成器就是協(xié)程的一種實現(xiàn)方式,這樣相信你也就理解什么是生成器了。那么接下來,我們使用生成器和 Promise 來改造開頭的那段 Promise 代碼。改造后的代碼如下所示:

//foo函數(shù)
function* foo() {
    let response1 = yield fetch('https://www.geekbang.org')
    console.log('response1')
    console.log(response1)
    let response2 = yield fetch('https://www.geekbang.org/test')
    console.log('response2')
    console.log(response2)
}

//執(zhí)行foo函數(shù)的代碼
let gen = foo()
function getGenPromise(gen) {
    return gen.next().value
}
getGenPromise(gen).then((response) => {
    console.log('response1')
    console.log(response)
    return getGenPromise(gen)
}).then((response) => {
    console.log('response2')
    console.log(response)
})

從圖中可以看到,foo 函數(shù)是一個生成器函數(shù),在 foo 函數(shù)里面實現(xiàn)了用同步代碼形式來實現(xiàn)異步操作;但是在 foo 函數(shù)外部,我們還需要寫一段執(zhí)行 foo 函數(shù)的代碼,如上述代碼的后半部分所示,那下面我們就來分析下這段代碼是如何工作的。

  • 首先執(zhí)行的是let gen = foo(),創(chuàng)建了 gen 協(xié)程。然后在父協(xié)程中通過執(zhí)行 gen.next 把主線程的控制權(quán)交給 gen 協(xié)程。
  • gen 協(xié)程獲取到主線程的控制權(quán)后,就調(diào)用 fetch 函數(shù)創(chuàng)建了一個 Promise 對象 response1,然后通過 yield 暫停 gen 協(xié)程的執(zhí)行,并將 response1 返回給父協(xié)程。
  • 父協(xié)程恢復(fù)執(zhí)行后,調(diào)用 response1.then 方法等待請求結(jié)果。
  • 等通過 fetch 發(fā)起的請求完成之后,會調(diào)用 then 中的回調(diào)函數(shù),then 中的回調(diào)函數(shù)拿到結(jié)果之后,通過調(diào)用 gen.next 放棄主線程的控制權(quán),將控制權(quán)交 gen 協(xié)程繼續(xù)執(zhí)行下個請求。

以上就是協(xié)程和 Promise 相互配合執(zhí)行的一個大致流程。不過通常,我們把執(zhí)行生成器的代碼封裝成一個函數(shù),并把這個執(zhí)行生成器代碼的函數(shù)稱為執(zhí)行器(可參考著名的 co 框架),如下面這種方式:

function* foo() {
    let response1 = yield fetch('https://www.geekbang.org')
    console.log('response1')
    console.log(response1)
    let response2 = yield fetch('https://www.geekbang.org/test')
    console.log('response2')
    console.log(response2)
}
co(foo());

通過使用生成器配合執(zhí)行器,就能實現(xiàn)使用同步的方式寫出異步代碼了,這樣也大大加強了代碼的可讀性。

async/await

雖然生成器已經(jīng)能很好地滿足我們的需求了,但是程序員的追求是無止境的,這不又在 ES7 中引入了 async/await,這種方式能夠徹底告別執(zhí)行器和生成器,實現(xiàn)更加直觀簡潔的代碼。其實 async/await 技術(shù)背后的秘密就是 Promise 和生成器應(yīng)用,往低層說就是微任務(wù)和協(xié)程應(yīng)用。要搞清楚 async 和 await 的工作原理,我們就得對 async 和 await 分開分析。

async

我們先來看看 async 到底是什么?根據(jù) MDN 定義,async 是一個通過異步執(zhí)行隱式返回 Promise 作為結(jié)果的函數(shù)。

這里我們先來看看是如何隱式返回 Promise 的,你可以參考下面的代碼:

async function foo() {
    return 2
}
console.log(foo())  // Promise {<resolved>: 2}

執(zhí)行這段代碼,我們可以看到調(diào)用 async 聲明的 foo 函數(shù)返回了一個 Promise 對象,狀態(tài)是 resolved,返回結(jié)果如下所示:

Promise {<resolved>: 2}

await

我們知道了 async 函數(shù)返回的是一個 Promise 對象,那下面我們再結(jié)合文中這段代碼來看看 await 到底是什么。

async function foo() {
    console.log(1)
    let a = await 100
    console.log(a)
    console.log(2)
}
console.log(0)
foo()
console.log(3)

觀察上面這段代碼,你能判斷出打印出來的內(nèi)容是什么嗎?這得先來分析 async 結(jié)合 await 到底會發(fā)生什么。在詳細介紹之前,我們先站在協(xié)程的視角來看看這段代碼的整體執(zhí)行流程圖:

結(jié)合上圖,我們來一起分析下 async/await 的執(zhí)行流程。

首先,執(zhí)行console.log(0)這個語句,打印出來 0。

緊接著就是執(zhí)行 foo 函數(shù),由于 foo 函數(shù)是被 async 標(biāo)記過的,所以當(dāng)進入該函數(shù)的時候,JavaScript 引擎會保存當(dāng)前的調(diào)用棧等信息,然后執(zhí)行 foo 函數(shù)中的console.log(1)語句,并打印出 1。

接下來就執(zhí)行到 foo 函數(shù)中的await 100這個語句了,這里是我們分析的重點,因為在執(zhí)行await 100這個語句時,JavaScript 引擎在背后為我們默默做了太多的事情,那么下面我們就把這個語句拆開,來看看 JavaScript 到底都做了哪些事情。

當(dāng)執(zhí)行到await 100時,會默認創(chuàng)建一個 Promise 對象,代碼如下所示

let promise_ = new Promise((resolve,reject){
  resolve(100)
})

在這個 promise_ 對象創(chuàng)建的過程中,我們可以看到在 executor 函數(shù)中調(diào)用了 resolve 函數(shù),JavaScript 引擎會將該任務(wù)提交給微任務(wù)隊列。

然后 JavaScript 引擎會暫停當(dāng)前協(xié)程的執(zhí)行,將主線程的控制權(quán)轉(zhuǎn)交給父協(xié)程執(zhí)行,同時會將 promise_ 對象返回給父協(xié)程。

主線程的控制權(quán)已經(jīng)交給父協(xié)程了,這時候父協(xié)程要做的一件事是調(diào)用 promise_.then 來監(jiān)控 promise 狀態(tài)的改變。接下來繼續(xù)執(zhí)行父協(xié)程的流程,這里我們執(zhí)行console.log(3),并打印出來 3。

隨后父協(xié)程將執(zhí)行結(jié)束,在結(jié)束之前,會進入微任務(wù)的檢查點,然后執(zhí)行微任務(wù)隊列,微任務(wù)隊列中有resolve(100)的任務(wù)等待執(zhí)行,執(zhí)行到這里的時候,會觸發(fā) promise_.then 中的回調(diào)函數(shù),如下所示:

promise_.then((value)=>{
   //回調(diào)函數(shù)被激活后
  //將主線程控制權(quán)交給foo協(xié)程,并將vaule值傳給協(xié)程
})

該回調(diào)函數(shù)被激活以后,會將主線程的控制權(quán)交給 foo 函數(shù)的協(xié)程,并同時將 value 值傳給該協(xié)程。

foo 協(xié)程激活之后,會把剛才的 value 值賦給了變量 a,然后 foo 協(xié)程繼續(xù)執(zhí)行后續(xù)語句,執(zhí)行完成之后,將控制權(quán)歸還給父協(xié)程。

以上就是 await/async 的執(zhí)行流程。正是因為 async 和 await 在背后為我們做了大量的工作,所以我們才能用同步的方式寫出異步代碼來。

小結(jié)

Promise 的編程模型依然充斥著大量的 then 方法,雖然解決了回調(diào)地獄的問題,但是在語義方面依然存在缺陷,代碼中充斥著大量的 then 函數(shù),這就是 async/await 出現(xiàn)的原因。

使用 async/await 可以實現(xiàn)用同步代碼的風(fēng)格來編寫異步代碼,這是因為 async/await 的基礎(chǔ)技術(shù)使用了生成器和 Promise,生成器是協(xié)程的實現(xiàn),利用生成器能實現(xiàn)生成器函數(shù)的暫停和恢復(fù)。

另外,V8 引擎還為 async/await 做了大量的語法層面包裝,所以了解隱藏在背后的代碼有助于加深你對 async/await 的理解。async/await 無疑是異步編程領(lǐng)域非常大的一個革新,也是未來的一個主流的編程風(fēng)格。

其實,除了 JavaScript,Python、Dart、C# 等語言也都引入了 async/await,使用它不僅能讓代碼更加整潔美觀,而且還能確保該函數(shù)始終都能返回 Promise。

總結(jié)

到此這篇關(guān)于JavaScript引擎實現(xiàn)async/await的文章就介紹到這了,更多相關(guān)js實現(xiàn)async/await內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • js Firefox 加入收藏夾功能代碼 兼容Firefox 和 IE

    js Firefox 加入收藏夾功能代碼 兼容Firefox 和 IE

    最近改用Firefox后,發(fā)現(xiàn)很多網(wǎng)站的“加入收藏”鏈接點擊無效了,后來發(fā)現(xiàn)原來是IE瀏覽器和Firefox瀏覽器的“加入收藏夾”的寫法是不同的。
    2009-12-12
  • Bootstrapvalidator校驗、校驗清除重置的實現(xiàn)代碼(推薦)

    Bootstrapvalidator校驗、校驗清除重置的實現(xiàn)代碼(推薦)

    這篇文章給大家介紹了bootstrapvalidator校驗、校驗清除重置的實現(xiàn)代碼,在代碼中需要我們引入css與js文件,大家可以參考下文的代碼
    2016-09-09
  • 純javascript實現(xiàn)自動發(fā)送郵件

    純javascript實現(xiàn)自動發(fā)送郵件

    當(dāng)我們發(fā)送郵件時,可以自定義郵件發(fā)送的時間,那么使用代碼是如何實現(xiàn)的呢?下面通過本篇文章給大家介紹使用純javascript實現(xiàn)自動發(fā)送郵件,感興趣的朋友可以參考下
    2015-10-10
  • 基于js實現(xiàn)數(shù)組相鄰元素上移下移

    基于js實現(xiàn)數(shù)組相鄰元素上移下移

    這篇文章主要介紹了基于js實現(xiàn)數(shù)組相鄰元素上移下移,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-05-05
  • 詳解JavaScript中的Map和WeakMap

    詳解JavaScript中的Map和WeakMap

    Map和WeakMap都是ES6中新增的數(shù)據(jù)結(jié)構(gòu),它們的主要區(qū)別在于鍵的作用域和鍵的類型,本文就通過代碼示例給大家詳細介紹了JavaScript鍵的作用域和類型,需要的朋友可以參考下
    2023-08-08
  • JavaScript學(xué)習(xí)筆記之基于定時器實現(xiàn)圖片無縫滾動功能詳解

    JavaScript學(xué)習(xí)筆記之基于定時器實現(xiàn)圖片無縫滾動功能詳解

    這篇文章主要介紹了JavaScript學(xué)習(xí)筆記之基于定時器實現(xiàn)圖片無縫滾動功能,結(jié)合實例形式分析了javascript定時器與頁面元素屬性動態(tài)設(shè)置等相關(guān)操作技巧,需要的朋友可以參考下
    2019-01-01
  • javascript 樹控件 比較好用

    javascript 樹控件 比較好用

    構(gòu)建樹控件的比較理想的一種方法是用ul、li這種簡單的標(biāo)簽組合來實現(xiàn),但是你如果想實現(xiàn)復(fù)雜一點的比如要節(jié)點可編輯、可拖動、可更換風(fēng)格等等我想ExtJS是最好的選擇了。
    2009-06-06
  • javaScript強制保留兩位小數(shù)的輸入數(shù)校驗和小數(shù)保留問題

    javaScript強制保留兩位小數(shù)的輸入數(shù)校驗和小數(shù)保留問題

    這篇文章主要介紹了javaScript強制保留兩位小數(shù)的輸入數(shù)校驗和小數(shù)保留問題,非常不錯,具有一定的參考借鑒價值,需要的朋友參考下吧
    2018-05-05
  • 淺談JS讀取DOM對象(標(biāo)簽)的自定義屬性

    淺談JS讀取DOM對象(標(biāo)簽)的自定義屬性

    下面小編就為大家?guī)硪黄獪\談JS讀取DOM對象(標(biāo)簽)的自定義屬性。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-11-11
  • JS獲取當(dāng)前時間的兩種方法小結(jié)

    JS獲取當(dāng)前時間的兩種方法小結(jié)

    這篇文章主要給大家介紹了關(guān)于JS獲取當(dāng)前時間的兩種方法,在web開發(fā)中,通過js獲取時間非常的常用,我這里做個總結(jié),需要的朋友可以參考下
    2023-09-09

最新評論