17. JavaScript 異步編程與回調(diào)

圖片來(lái)源網(wǎng)絡(luò),侵刪

編程語(yǔ)言中的異步性

計(jì)算機(jī)在設(shè)計(jì)上是異步的。

異步意味著事情可以獨(dú)立于主程序流而發(fā)生。

在當(dāng)前的用戶計(jì)算機(jī)中,每個(gè)程序都運(yùn)行于特定的時(shí)間段,然后停止執(zhí)行,以讓另一個(gè)程序繼續(xù)執(zhí)行。 這件事運(yùn)行得如此之快,以至于無(wú)法察覺(jué)。 我們以為計(jì)算機(jī)可以同時(shí)運(yùn)行許多程序,但這是一種錯(cuò)覺(jué)(在多處理器計(jì)算機(jī)上除外)。

程序在內(nèi)部會(huì)使用中斷,一種被發(fā)送到處理器以獲取系統(tǒng)關(guān)注的信號(hào)。

這里不會(huì)深入探討這個(gè)問(wèn)題,只要記住,程序是異步的且會(huì)暫停執(zhí)行直到需要關(guān)注,這使得計(jì)算機(jī)可以同時(shí)執(zhí)行其他操作。 當(dāng)程序正在等待來(lái)自網(wǎng)絡(luò)的響應(yīng)時(shí),則它無(wú)法在請(qǐng)求完成之前停止處理器。

通常,編程語(yǔ)言是同步的,有些會(huì)在語(yǔ)言或庫(kù)中提供管理異步性的方法。 默認(rèn)情況下,C、Java、C#、PHP、Go、Ruby、Swift 和 Python 都是同步的。 其中一些語(yǔ)言通過(guò)使用線程(衍生新的進(jìn)程)來(lái)處理異步操作。

JavaScript

JavaScript 默認(rèn)情況下是同步的,并且是單線程的。 這意味著代碼無(wú)法創(chuàng)建新的線程并且不能并行運(yùn)行。

代碼行是依次執(zhí)行的,例如:

const a = 1
const b = 2
const c = a * b
console.log(c)
doSomething()

但是 JavaScript 誕生于瀏覽器內(nèi)部,一開(kāi)始的主要工作是響應(yīng)用戶的操作,例如 onClick、onMouseOver、onChangeonSubmit 等。 使用同步的編程模型該如何做到這一點(diǎn)?

答案就在于它的環(huán)境。 瀏覽器通過(guò)提供一組可以處理這種功能的 API 來(lái)提供了一種實(shí)現(xiàn)方式。

更近點(diǎn),Node.js 引入了非阻塞的 I/O 環(huán)境,以將該概念擴(kuò)展到文件訪問(wèn)、網(wǎng)絡(luò)調(diào)用等。

回調(diào)

你不知道用戶何時(shí)單擊按鈕。 因此,為點(diǎn)擊事件定義了一個(gè)事件處理程序。 該事件處理程序會(huì)接受一個(gè)函數(shù),該函數(shù)會(huì)在該事件被觸發(fā)時(shí)被調(diào)用:

document.getElementById('button').addEventListener('click', () => {
  //被點(diǎn)擊
})

這就是所謂的回調(diào)。

回調(diào)是一個(gè)簡(jiǎn)單的函數(shù),會(huì)作為值被傳給另一個(gè)函數(shù),并且僅在事件發(fā)生時(shí)才被執(zhí)行。 之所以這樣做,是因?yàn)?JavaScript 具有頂級(jí)的函數(shù),這些函數(shù)可以被分配給變量并傳給其他函數(shù)(稱為高階函數(shù))。

通常會(huì)將所有的客戶端代碼封裝在 window 對(duì)象的 load 事件監(jiān)聽(tīng)器中,其僅在頁(yè)面準(zhǔn)備就緒時(shí)才會(huì)運(yùn)行回調(diào)函數(shù):

window.addEventListener('load', () => {
  //window 已被加載。
  //做需要做的。
})

回調(diào)無(wú)處不在,不僅在 DOM 事件中。

一個(gè)常見(jiàn)的示例是使用定時(shí)器:

setTimeout(() => {
  // 2 秒之后運(yùn)行。
}, 2000)

XHR 請(qǐng)求也接受回調(diào),在此示例中,會(huì)將一個(gè)函數(shù)分配給一個(gè)屬性,該屬性會(huì)在發(fā)生特定事件(在該示例中,是請(qǐng)求狀態(tài)的改變)時(shí)被調(diào)用:

const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
  if (xhr.readyState === 4) {
    xhr.status === 200 ? console.log(xhr.responseText) : console.error('出錯(cuò)')
  }
}
xhr.open('GET', 'http://nodejs.cn')
xhr.send()

處理回調(diào)中的錯(cuò)誤

如何處理回調(diào)的錯(cuò)誤? 一種非常常見(jiàn)的策略是使用 Node.js 所采用的方式:任何回調(diào)函數(shù)中的第一個(gè)參數(shù)為錯(cuò)誤對(duì)象(即錯(cuò)誤優(yōu)先的回調(diào))。

如果沒(méi)有錯(cuò)誤,則該對(duì)象為 null。 如果有錯(cuò)誤,則它會(huì)包含對(duì)該錯(cuò)誤的描述以及其他信息。

fs.readFile('/文件.json', (err, data) => {
  if (err !== null) {
    //處理錯(cuò)誤
    console.log(err)
    return
  }

  //沒(méi)有錯(cuò)誤,則處理數(shù)據(jù)。
  console.log(data)
})

回調(diào)的問(wèn)題

回調(diào)適用于簡(jiǎn)單的場(chǎng)景!

但是,每個(gè)回調(diào)都可以添加嵌套的層級(jí),并且當(dāng)有很多回調(diào)時(shí),代碼就會(huì)很快變得非常復(fù)雜:

window.addEventListener('load', () => {
  document.getElementById('button').addEventListener('click', () => {
    setTimeout(() => {
      items.forEach(item => {
        //你的代碼在這里。
      })
    }, 2000)
  })
})

這只是一個(gè)簡(jiǎn)單的 4 個(gè)層級(jí)的代碼,但還有更多層級(jí)的嵌套,這很不好。

該如何解決?

回調(diào)的替代方法

從 ES6 開(kāi)始,JavaScript 引入了一些特性,可以幫助處理異步代碼而不涉及使用回調(diào):Promise(ES6)和 Async/Await(ES2017)。
文章來(lái)源 node中文官方 http://nodejs.cn/

更多知識(shí)點(diǎn) 請(qǐng)關(guān)注:筆墨是小舟

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容