成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

異步的JavaScript

tangr206 / 2917人閱讀

摘要:如果在瀏覽器中線程阻塞了,瀏覽器可能會(huì)失去響應(yīng),從而造成不好的用戶體驗(yàn)。中也有可能會(huì)產(chǎn)生新的,會(huì)進(jìn)入尾部,并在本次前執(zhí)行。這就是所謂的,而把回調(diào)函數(shù)的嵌套邏輯替換成了符合正常人思維習(xí)慣的線性邏輯。

JS本身是一門單線程的語言,所以在執(zhí)行一些需要等待的任務(wù)(eg.等待服務(wù)器響應(yīng),等待用戶輸入等)時(shí)就會(huì)阻塞其他代碼。如果在瀏覽器中JS線程阻塞了,瀏覽器可能會(huì)失去響應(yīng),從而造成不好的用戶體驗(yàn)。幸運(yùn)的是JS語言本身和其運(yùn)行的環(huán)境(瀏覽器,Node)都提供了一些解決方案讓JS可以“異步”起來,在此梳理一下相關(guān)的知識(shí)點(diǎn),如果你讀完之后有所收獲,那更是極好的。

Event Loop

JS中每個(gè)函數(shù)都伴有一個(gè)自身的作用域(execution context),這個(gè)作用域包含函數(shù)的一些信息(eg.參數(shù),局部變量等),在函數(shù)被調(diào)用時(shí),函數(shù)的作用域?qū)ο蟊煌迫雸?zhí)行棧(execution context stack),執(zhí)行完畢后出棧。當(dāng)執(zhí)行一些異步任務(wù)時(shí),JS僅調(diào)用相應(yīng)的API并不去等待任務(wù)結(jié)果而是繼續(xù)執(zhí)行后續(xù)代碼,這些異步任務(wù)被瀏覽器或者Node交由其他線程執(zhí)行(eg.定時(shí)器線程、http請(qǐng)求線程、DOM事件線程等),完成之后這些異步任務(wù)的回調(diào)函數(shù)會(huì)被推入相應(yīng)的隊(duì)列中,直到執(zhí)行棧為空時(shí),這些回調(diào)函數(shù)才會(huì)被依次執(zhí)行。

舉個(gè)例子:

function main() {
  console.log("A)

  setTimeout(function display() {
    console.log("B")
  }, 0)

  console.log("C")
}

main()

以上代碼在Event Loop中的執(zhí)行過程如下:

類似于setTimeout這樣的任務(wù)還有:setInterval, setImmediate, 響應(yīng)用戶操作的事件(eg. click, input等), 響應(yīng)網(wǎng)絡(luò)請(qǐng)求(eg. ajax的onload,image的onload等),數(shù)據(jù)庫操作等等。這些操作有一個(gè)統(tǒng)一的名字:task,所以上圖中的message queue其實(shí)是task queue,因?yàn)檫€存在一些像:Promise,process.nextTick, MutationObserver之類的任務(wù),這些任務(wù)叫做microtask,__microtask會(huì)在代碼執(zhí)行過程中被推入microtask queue而不是task queue__,microtask queue中的任務(wù)同樣也需要等待執(zhí)行棧為空時(shí)依次執(zhí)行。

一個(gè)task中可能會(huì)產(chǎn)生microtask和新的task,其中產(chǎn)生的microtask會(huì)在本次task結(jié)束后,即執(zhí)行棧為空時(shí)執(zhí)行,而新的task則會(huì)在render之后執(zhí)行。microtask中也有可能會(huì)產(chǎn)生新的microtask,會(huì)進(jìn)入microtask queue尾部,并在本次render前執(zhí)行。

這樣的流程是有它存在原因的,這里僅僅談下我個(gè)人的理解,如有錯(cuò)誤,還請(qǐng)指出:
瀏覽器中除了JS引擎線程,還存在GUI渲染線程,用以解析HTML, CSS, 構(gòu)建DOM樹等工作,然而這兩個(gè)線程是互斥的,只有在JS引擎線程空閑時(shí),GUI渲染線程才有可能執(zhí)行。在兩個(gè)task之間,JS引擎空閑,此時(shí)如果GUI渲染隊(duì)列不為空,瀏覽器就會(huì)切換至GUI渲染線程進(jìn)行render工作。而microtask會(huì)在render之前執(zhí)行,旨在以類似同步的方式(盡可能快地)執(zhí)行異步任務(wù),所以microtask執(zhí)行時(shí)間過長(zhǎng)就會(huì)阻塞頁面的渲染。

setTimeout、setInterval、requestAnimationFrame

上文提到setTimeout,setInterval都屬于task,所以即便設(shè)置間隔為0:

setTimeout(function display() {
  console.log("B")
}, 0)

回調(diào)也會(huì)異步執(zhí)行。

setTimeout,setInterval常被用于編寫JS動(dòng)畫,比如:

// setInterval
function draw() {
  // ...some draw code
}

var intervalTimer = setInterval(draw, 500)

// setTimeout
var timeoutTimer = null

function move() {
  // ...some move code

  timeoutTimer = setTimeout(move, 500)
}

move()

這其實(shí)是存在一定的問題的:

從event loop的角度分析:setInterval的兩次回調(diào)之間的間隔是不確定的,取決于回調(diào)中的代碼的執(zhí)行時(shí)間;

從性能的角度分析:無論是setInterval還是setTimeout都“無法感知瀏覽器當(dāng)前的工作狀態(tài)”,比如當(dāng)前頁面為隱藏tab,或者設(shè)置動(dòng)畫的元素不在當(dāng)前viewport,setInterval & setTimeout仍會(huì)照常執(zhí)行,實(shí)際是沒有必要的,雖然某些瀏覽器像Chrome會(huì)優(yōu)化這種情況,但不能保證所有的瀏覽器都會(huì)有優(yōu)化措施。再比如多個(gè)元素同時(shí)執(zhí)行不同的動(dòng)畫,可能會(huì)造成不必要的重繪,其實(shí)頁面只需要重繪一次即可。

在這種背景下,Mozilla提出了requestAnimationFrame,后被Webkit優(yōu)化并采用,requestAnimationFrame為編寫JS動(dòng)畫提供了原生API。
function draw() {
  // ...some draw code

  requestAnimationFrame(draw)
}

draw()

requestAnimationFrame為JS動(dòng)畫做了一些優(yōu)化:

大多數(shù)屏幕的最高幀率是60fps,requestAnimationFrame默認(rèn)會(huì)盡可能地達(dá)到這一幀率

元素不在當(dāng)前viewport時(shí),requestAnimationFrame會(huì)極大地限制動(dòng)畫的幀率以節(jié)約系統(tǒng)資源

使用requestAnimationFrame定義多個(gè)同時(shí)段的動(dòng)畫,頁面只會(huì)產(chǎn)生一次重繪。

當(dāng)然requestAnimationFrame存在一定的兼容性問題,具體可參考 can i use。

Promise
fs.readdir(source, function (err, files) {
  if (err) {
    console.log("Error finding files: " + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log("Error identifying file size: " + err)
        } else {
          console.log(filename + " : " + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log("resizing " + filename + "to " + height + "x" + height)
            this.resize(width, height).write(dest + "w" + width + "_" + filename, function(err) {
              if (err) console.log("Error writing file: " + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

假設(shè)最初學(xué)JS時(shí)我看到的是上面的代碼,我一定不會(huì)想寫前端。這就是所謂的“callback hell”,而Promise把回調(diào)函數(shù)的嵌套邏輯替換成了符合正常人思維習(xí)慣的線性邏輯。

function fetchSomething() {
    return new Promise(function(resolved) {
        if (success) {
            resolved(res);
        }
    });
}
fetchSomething().then(function(res) {
    console.log(res);
    return fetchSomething();
}).then(function(res) {
    console.log("duplicate res");
    return "done";
}).then(function(tip) {
    console.log(tip);
})
async await

async await是ES2017引入的兩個(gè)關(guān)鍵字,旨在讓開發(fā)者更方便地編寫異步代碼,可是往往能看到類似這樣的代碼:

async function orderFood() {
  const pizzaData = await getPizzaData()    // async call
  const drinkData = await getDrinkData()    // async call
  const chosenPizza = choosePizza()    // sync call
  const chosenDrink = chooseDrink()    // sync call

  await addPizzaToCart(chosenPizza)    // async call
  await addDrinkToCart(chosenDrink)    // async call

  orderItems()    // async call
}

Promise的引入讓我們脫離了“callback hell”,可是對(duì)async函數(shù)的錯(cuò)誤用法又讓我們陷入了“async hell”。

這里其實(shí)getPizzaData和getDrinkData是沒有關(guān)聯(lián)的,而await關(guān)鍵字使得必須在getPizzaData resolve之后才能執(zhí)行g(shù)etDrinkData的動(dòng)作,這顯然是冗余的,包括addPizzaToCart和addDrinkToCart也是一樣,影響了系統(tǒng)的性能。所以在寫async函數(shù)時(shí),應(yīng)該清楚哪些代碼是相互依賴的,把這些代碼多帶帶抽成async函數(shù),另外Promise在聲明時(shí)就已經(jīng)執(zhí)行,提前執(zhí)行這些抽出來的async函數(shù),再await其結(jié)果就能避免“async hell”,或者也可以用Promise.all():

async function selectPizza() {
  const pizzaData = await getPizzaData()    // async call
  const chosenPizza = choosePizza()    // sync call

  await addPizzaToCart(chosenPizza)    // async call
}

async function selectDrink() {
  const drinkData = await getDrinkData()    // async call
  const chosenDrink = chooseDrink()    // sync call

  await addDrinkToCart(chosenDrink)    // async call
}

// return promise early
async function orderFood() {
  const pizzaPromise = selectPizza()
  const drinkPromise = selectDrink()

  await pizzaPromise
  await drinkPromise

  orderItems()    // async call
}

// or promise.all()
Promise.all([selectPizza(), selectDrink()]).then(orderItems)   // async call
參考文章 && 拓展閱讀

JavaScript Event Loop Explained

How to escape async/await hell

Tasks, microtasks, queues and schedules

requestAnimationFrame

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109131.html

相關(guān)文章

  • 淺析JavaScript異步

    摘要:回調(diào)函數(shù),一般在同步情境下是最后執(zhí)行的,而在異步情境下有可能不執(zhí)行,因?yàn)槭录]有被觸發(fā)或者條件不滿足。同步方式請(qǐng)求異步同步請(qǐng)求當(dāng)請(qǐng)求開始發(fā)送時(shí),瀏覽器事件線程通知主線程,讓線程發(fā)送數(shù)據(jù)請(qǐng)求,主線程收到 一直以來都知道JavaScript是一門單線程語言,在筆試過程中不斷的遇到一些輸出結(jié)果的問題,考量的是對(duì)異步編程掌握情況。一般被問到異步的時(shí)候腦子里第一反應(yīng)就是Ajax,setTimse...

    Tangpj 評(píng)論0 收藏0
  • JavaScript 異步

    摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。寫一個(gè)符合規(guī)范并可配合使用的寫一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過...

    tuniutech 評(píng)論0 收藏0
  • javascript異步與promise

    摘要:到這里,我已經(jīng)發(fā)出了一個(gè)請(qǐng)求買漢堡,啟動(dòng)了一次交易。但是做漢堡需要時(shí)間,我不能馬上得到這個(gè)漢堡,收銀員給我一個(gè)收據(jù)來代替漢堡。到這里,收據(jù)就是一個(gè)承諾保證我最后能得到漢堡。 同期異步系列文章推薦談一談javascript異步j(luò)avascript異步中的回調(diào)javascript異步之Promise.all()、Promise.race()、Promise.finally()javascr...

    rollback 評(píng)論0 收藏0
  • 夯實(shí)基礎(chǔ)-JavaScript異步編程

    摘要:調(diào)用棧被清空,消息隊(duì)列中并無任務(wù),線程停止,事件循環(huán)結(jié)束。不確定的時(shí)間點(diǎn)請(qǐng)求返回,將設(shè)定好的回調(diào)函數(shù)放入消息隊(duì)列。調(diào)用棧執(zhí)行完畢執(zhí)行消息隊(duì)列任務(wù)。請(qǐng)求并發(fā)回調(diào)函數(shù)執(zhí)行順序無法確定。 異步編程 JavaScript中異步編程問題可以說是基礎(chǔ)中的重點(diǎn),也是比較難理解的地方。首先要弄懂的是什么叫異步? 我們的代碼在執(zhí)行的時(shí)候是從上到下按順序執(zhí)行,一段代碼執(zhí)行了之后才會(huì)執(zhí)行下一段代碼,這種方式...

    shadowbook 評(píng)論0 收藏0
  • 談一談javascript異步

    摘要:從今天開始研究一下的異步相關(guān)內(nèi)容,感興趣的請(qǐng)關(guān)注同期異步系列文章推薦異步中的回調(diào)異步與異步之異步之異步之和異步之一異步之二異步實(shí)戰(zhàn)異步總結(jié)歸檔什么是異步我們知道的單線程的,這與它的用途有關(guān)。 從今天開始研究一下javascript的異步相關(guān)內(nèi)容,感興趣的請(qǐng)關(guān)注 同期異步系列文章推薦javascript異步中的回調(diào)javascript異步與promisejavascript異步之Prom...

    Sourcelink 評(píng)論0 收藏0
  • 異步

    摘要:在異步機(jī)制中,任務(wù)隊(duì)列就是用來維護(hù)異步任務(wù)回調(diào)函數(shù)的隊(duì)列。四對(duì)象對(duì)象是工作組提出的一種規(guī)范,目的是為異步編程提供統(tǒng)一接口。 異步 1.JavaScript單線程的理解 Javascript語言的執(zhí)行環(huán)境是單線程(single thread)。所謂單線程,就是指一次只能完成一件任務(wù)。如果有多個(gè)任務(wù),就必須排隊(duì),前面一個(gè)任務(wù)完成,再執(zhí)行后面一個(gè)任務(wù),以此類推。 2.JavaScript單線...

    goji 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<