W3Cschool
恭喜您成為首批注冊(cè)用戶(hù)
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
我們?cè)谶@里的示例中使用了瀏覽器方法
為了演示回調(diào)、promise 和其他抽象概念的使用,我們將使用一些瀏覽器方法:具體地說(shuō),是加載腳本和執(zhí)行簡(jiǎn)單的文檔操作的方法。
如果你不熟悉這些方法,并且對(duì)它們?cè)谶@些示例中的用法感到疑惑,那么你可能需要閱讀本教程 下一部分 中的幾章。
但是,我們會(huì)盡全力使講解變得更加清晰。在這兒不會(huì)有瀏覽器方面的真正復(fù)雜的東西。
JavaScript 主機(jī)(host)環(huán)境提供了許多函數(shù),這些函數(shù)允許我們計(jì)劃 異步 行為(action)。換句話(huà)說(shuō),我們現(xiàn)在開(kāi)始執(zhí)行的行為,但它們會(huì)在稍后完成。
例如,setTimeout
函數(shù)就是一個(gè)這樣的函數(shù)。
這兒有一些實(shí)際中的異步行為的示例,例如加載腳本和模塊(我們將在后面的章節(jié)中介紹)。
讓我們看一下函數(shù) loadScript(src)
,該函數(shù)使用給定的 src
加載腳本:
function loadScript(src) {
// 創(chuàng)建一個(gè) <script> 標(biāo)簽,并將其附加到頁(yè)面
// 這將使得具有給定 src 的腳本開(kāi)始加載,并在加載完成后運(yùn)行
let script = document.createElement('script');
script.src = src;
document.head.append(script);
}
它將一個(gè)新的、帶有給定 src
的、動(dòng)態(tài)創(chuàng)建的標(biāo)簽 <script src="…">
插入到文檔中。瀏覽器將自動(dòng)開(kāi)始加載它,并在加載完成后執(zhí)行它。
我們可以像這樣使用這個(gè)函數(shù):
// 在給定路徑下加載并執(zhí)行腳本
loadScript('/my/script.js');
腳本是“異步”調(diào)用的,因?yàn)樗鼜默F(xiàn)在開(kāi)始加載,但是在這個(gè)加載函數(shù)執(zhí)行完成后才運(yùn)行。
如果在 loadScript(…)
下面有任何其他代碼,它們不會(huì)等到腳本加載完成才執(zhí)行。
loadScript('/my/script.js');
// loadScript 下面的代碼
// 不會(huì)等到腳本加載完成才執(zhí)行
// ...
假設(shè)我們需要在新腳本加載后立即使用它。它聲明了新函數(shù),我們想運(yùn)行它們。
但如果我們?cè)?nbsp;loadScript(…)
調(diào)用后立即執(zhí)行此操作,這將不會(huì)有效。
loadScript('/my/script.js'); // 這個(gè)腳本有 "function newFunction() {…}"
newFunction(); // 沒(méi)有這個(gè)函數(shù)!
自然情況下,瀏覽器可能沒(méi)有時(shí)間加載腳本。到目前為止,loadScript
函數(shù)并沒(méi)有提供跟蹤加載完成的方法。腳本加載并最終運(yùn)行,僅此而已。但我們希望了解腳本何時(shí)加載完成,以使用其中的新函數(shù)和變量。
讓我們添加一個(gè) callback
函數(shù)作為 loadScript
的第二個(gè)參數(shù),該函數(shù)應(yīng)在腳本加載完成時(shí)執(zhí)行:
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
onload
事件在 資源加載:onload,onerror 一文中有描述,它通常會(huì)在腳本加載和執(zhí)行完成后執(zhí)行一個(gè)函數(shù)。
現(xiàn)在,如果我們想調(diào)用該腳本中的新函數(shù),我們應(yīng)該將其寫(xiě)在回調(diào)函數(shù)中:
loadScript('/my/script.js', function() {
// 在腳本加載完成后,回調(diào)函數(shù)才會(huì)執(zhí)行
newFunction(); // 現(xiàn)在它工作了
...
});
這是我們的想法:第二個(gè)參數(shù)是一個(gè)函數(shù)(通常是匿名函數(shù)),該函數(shù)會(huì)在行為(action)完成時(shí)運(yùn)行。
這是一個(gè)帶有真實(shí)腳本的可運(yùn)行的示例:
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
alert(`酷,腳本 ${script.src} 加載完成`);
alert( _ ); // _ 是所加載的腳本中聲明的一個(gè)函數(shù)
});
這被稱(chēng)為“基于回調(diào)”的異步編程風(fēng)格。異步執(zhí)行某項(xiàng)功能的函數(shù)應(yīng)該提供一個(gè) callback
參數(shù)用于在相應(yīng)事件完成時(shí)調(diào)用。(譯注:上面這個(gè)例子中的相應(yīng)事件是指腳本加載)
這里我們?cè)?nbsp;loadScript
中就是這么做的,但當(dāng)然這是一種通用方法。
我們?nèi)绾我来渭虞d兩個(gè)腳本:第一個(gè),然后是第二個(gè)?
自然的解決方案是將第二個(gè) loadScript
調(diào)用放入回調(diào)中,如下所示:
loadScript('/my/script.js', function(script) {
alert(`酷,腳本 ${script.src} 加載完成,讓我們繼續(xù)加載另一個(gè)吧`);
loadScript('/my/script2.js', function(script) {
alert(`酷,第二個(gè)腳本加載完成`);
});
});
在外部 loadScript
執(zhí)行完成時(shí),回調(diào)就會(huì)發(fā)起內(nèi)部的 loadScript
。
如果我們還想要一個(gè)腳本呢?
loadScript('/my/script.js', function(script) {
loadScript('/my/script2.js', function(script) {
loadScript('/my/script3.js', function(script) {
// ...加載完所有腳本后繼續(xù)
});
});
});
因此,每一個(gè)新行為(action)都在回調(diào)內(nèi)部。這對(duì)于幾個(gè)行為來(lái)說(shuō)還好,但對(duì)于許多行為來(lái)說(shuō)就不好了,所以我們很快就會(huì)看到其他變體。
在上述示例中,我們并沒(méi)有考慮出現(xiàn) error 的情況。如果腳本加載失敗怎么辦?我們的回調(diào)應(yīng)該能夠?qū)Υ俗鞒龇磻?yīng)。
這是 loadScript
的改進(jìn)版本,可以跟蹤加載錯(cuò)誤:
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`Script load error for ${src}`));
document.head.append(script);
}
加載成功時(shí),它會(huì)調(diào)用 callback(null, script)
,否則調(diào)用 callback(error)
。
用法:
loadScript('/my/script.js', function(error, script) {
if (error) {
// 處理 error
} else {
// 腳本加載成功
}
});
再次強(qiáng)調(diào),我們?cè)?nbsp;loadScript
中所使用的方案其實(shí)很普遍。它被稱(chēng)為“Error 優(yōu)先回調(diào)(error-first callback)”風(fēng)格。
約定是:
callback
? 的第一個(gè)參數(shù)是為 error 而保留的。一旦出現(xiàn) error,?callback(err)
? 就會(huì)被調(diào)用。callback(null, result1, result2…)
? 就會(huì)被調(diào)用。因此,單一的 callback
函數(shù)可以同時(shí)具有報(bào)告 error 和傳遞返回結(jié)果的作用。
乍一看,它像是一種可行的異步編程方式。的確如此,對(duì)于一個(gè)或兩個(gè)嵌套的調(diào)用看起來(lái)還不錯(cuò)。
但對(duì)于一個(gè)接一個(gè)的多個(gè)異步行為,代碼將會(huì)變成這樣:
loadScript('1.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...加載完所有腳本后繼續(xù) (*)
}
});
}
});
}
});
在上面這段代碼中:
1.js
?,如果沒(méi)有發(fā)生錯(cuò)誤。2.js
?,如果沒(méi)有發(fā)生錯(cuò)誤……3.js
?,如果沒(méi)有發(fā)生錯(cuò)誤 —— 做其他操作 ?(*)
?。隨著調(diào)用嵌套的增加,代碼層次變得更深,維護(hù)難度也隨之增加,尤其是我們使用的是可能包含了很多循環(huán)和條件語(yǔ)句的真實(shí)代碼,而不是例子中的 ...
。
有時(shí)這些被稱(chēng)為“回調(diào)地獄”或“厄運(yùn)金字塔”。
嵌套調(diào)用的“金字塔”隨著每個(gè)異步行為會(huì)向右增長(zhǎng)。很快它就失控了。
所以這種編碼方式不是很好。
我們可以通過(guò)使每個(gè)行為都成為一個(gè)獨(dú)立的函數(shù)來(lái)嘗試減輕這種問(wèn)題,如下所示:
loadScript('1.js', step1);
function step1(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', step2);
}
}
function step2(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', step3);
}
}
function step3(error, script) {
if (error) {
handleError(error);
} else {
// ...加載完所有腳本后繼續(xù) (*)
}
}
看到了嗎?它的作用相同,但是沒(méi)有深層的嵌套了,因?yàn)槲覀儗⒚總€(gè)行為都編寫(xiě)成了一個(gè)獨(dú)立的頂層函數(shù)。
它可以工作,但是代碼看起來(lái)就像是一個(gè)被撕裂的表格。你可能已經(jīng)注意到了,它的可讀性很差,在閱讀時(shí)你需要在各個(gè)代碼塊之間跳轉(zhuǎn)。這很不方便,特別是如果讀者對(duì)代碼不熟悉,他們甚至不知道應(yīng)該跳轉(zhuǎn)到什么地方。
此外,名為 ?step*
? 的函數(shù)都是一次性使用的,創(chuàng)建它們就是為了避免“厄運(yùn)金字塔”。沒(méi)有人會(huì)在行為鏈之外重用它們。因此,這里的命名空間有點(diǎn)混亂。
我們希望還有更好的方法。
幸運(yùn)的是,有其他方法可以避免此類(lèi)金字塔。最好的方法之一就是 “promise”,我們將在下一章中介紹它。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話(huà):173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: