
編程語(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、onChange、onSubmit 等。 使用同步的編程模型該如何做到這一點(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)注:筆墨是小舟