摘要:實(shí)際上,系統(tǒng)維護(hù)了一個(gè)按事件發(fā)生順序排列的內(nèi)部事件隊(duì)列,一次調(diào)用一個(gè)已注冊(cè)的回調(diào)函數(shù)。提示異步使用回調(diào)函數(shù)來(lái)延緩處理代價(jià)高昂的操作以避免阻塞主應(yīng)用程序。這具有幾乎立刻將回調(diào)函數(shù)添加到事件隊(duì)列上的作用。
參考書(shū)籍:《Effective JavaScript》
并發(fā)在JavaScript中,編寫(xiě)響應(yīng)多個(gè)并發(fā)事件的程序的方法非常人性化,而且強(qiáng)大,因?yàn)樗褂昧艘粋€(gè)簡(jiǎn)單的執(zhí)行模型(有時(shí)稱為事件隊(duì)列或事件循環(huán)并發(fā))和被稱為異步的API。
不要阻塞I/O事件隊(duì)列在一些語(yǔ)言中,我們會(huì)習(xí)慣性地編寫(xiě)代碼來(lái)等待某個(gè)特定的輸入。
var text = downloadSync("http://example.com/file.txt"); console.log(text);
形如downloadSync這樣的函數(shù)被稱為同步函數(shù)(或阻塞函數(shù))。程序會(huì)停止做任何工作,而等待它的輸入。在這個(gè)例子中,也就是等待從網(wǎng)絡(luò)下載文件的結(jié)果。由于在等待下載完成的期間,計(jì)算機(jī)可以做其他有用的工作,因此這樣的語(yǔ)言通常為程序員提供一種方法來(lái)創(chuàng)建多個(gè)線程,即并行執(zhí)行自己算。它允許程序的一部分停下來(lái)等待(阻塞)一個(gè)低速的輸入,而程序的另一部分可以繼續(xù)進(jìn)行獨(dú)立的工作。
在JavaScript中,大多的I/O操作都提供了異步的或非阻塞的API。
downloadAsync("http://example.com/file.txt", function (text) { console.log(text); });
該API初始化下載進(jìn)程,然后在內(nèi)部注冊(cè)表中存儲(chǔ)了回調(diào)函數(shù)后立刻返回,而不是被網(wǎng)絡(luò)請(qǐng)求阻塞。
JavaScript有時(shí)被稱為提供一個(gè)運(yùn)行到完成機(jī)制(run-to-completion)的擔(dān)保。任何當(dāng)前正在運(yùn)行于共享上下文的用戶代碼,比如瀏覽器中的單個(gè)Web頁(yè)面或者單個(gè)運(yùn)行的Web服務(wù)器實(shí)例,只有在執(zhí)行完成后才能調(diào)用下一個(gè)事件處理程序。實(shí)際上,系統(tǒng)維護(hù)了一個(gè)按事件發(fā)生順序排列的內(nèi)部事件隊(duì)列,一次調(diào)用一個(gè)已注冊(cè)的回調(diào)函數(shù)。
以客戶端(mouse moved、file downloaded)和服務(wù)器端(file read、path resolved)應(yīng)用程序事件為例,隨著事件的發(fā)生,它們被添加到應(yīng)用程序的事件隊(duì)列的末尾。JavaScript系統(tǒng)使用一個(gè)內(nèi)部循環(huán)機(jī)制來(lái)執(zhí)行應(yīng)用程序。該循環(huán)機(jī)制每次都拉取隊(duì)列底部的事件,也就是說(shuō),以接收到這些事件的順序來(lái)調(diào)用這些已注冊(cè)的JavaScript事件處理程序,并將事件的數(shù)據(jù)作為改事件處理程序的參數(shù)。
運(yùn)行到完成機(jī)制擔(dān)保的好處是當(dāng)代碼運(yùn)行時(shí),你完全掌握應(yīng)用程序的狀態(tài)。你根本不必?fù)?dān)心一些變量和對(duì)象屬性的改變由于并發(fā)執(zhí)行代碼而超出你的控制。并發(fā)編程在JavaScript中往往比使用線程和鎖的C++、Java或C#要容易得多。
然而,運(yùn)行到完成機(jī)制的不足是,實(shí)際上所有你編寫(xiě)的代碼支撐著余下應(yīng)用程序的繼續(xù)執(zhí)行。
JavaScript并發(fā)的一個(gè)最重要的規(guī)則是絕不要在應(yīng)用程序事件隊(duì)列中使用阻塞I/O的API。
相比之下,異步的API用在基于事件的環(huán)境中是安全的,因?yàn)樗鼈兤仁箲?yīng)用程序邏輯在一個(gè)獨(dú)立的事件循環(huán)“輪詢”中繼續(xù)處理。
提示:
異步APi使用回調(diào)函數(shù)來(lái)延緩處理代價(jià)高昂的操作以避免阻塞主應(yīng)用程序。
JavaScript并發(fā)地接收事件,但會(huì)使用一個(gè)事件隊(duì)列按序地處理事件處理程序。
在應(yīng)用程序事件隊(duì)列中絕不要使用阻塞的I/O。
在異步序列中使用嵌套或命名的回調(diào)函數(shù)理解操作序列的最簡(jiǎn)單的方式是異步API是發(fā)起操作而不是執(zhí)行操作。異步操作完成后,在事件循環(huán)的某個(gè)多帶帶的輪次中,被注冊(cè)的事件處理程序才會(huì)執(zhí)行。
如果你需要在發(fā)起一個(gè)操作后做一些事情,如何串聯(lián)已完成的異步操作。
最簡(jiǎn)單的答案是使用嵌套。
db.lookupAsyc("url", function(url) { downloadAsyc(url, function(text) { console.log("contents of " + url + ": " + text); }); });
嵌套的異步操作很容易,但當(dāng)擴(kuò)展到更長(zhǎng)的序列時(shí)會(huì)很快變得笨拙。
db.lookupAsync("url", function(url) { downloadAsync(url, function(file) { downloadAsync("a.txt", function(a) { downladAsync("b.txt", function(b) { downloadAsync("c.txt", function(c) { // ... }); }) }); }); });
減少過(guò)多嵌套的方法之一是將嵌套的回調(diào)函數(shù)作為命名的函數(shù),并將它們需要的附加數(shù)據(jù)作為額外的參數(shù)傳遞。
db.lookupAsync("url", downloadURL); function downloadURL(url) { downloadAsync(url, function(text) { // still nested showContents(url, text); }); } function showContents(url, text) { console.log("contents of " + url + ": " + text); }
上述代碼仍然使用了嵌套的回調(diào)函數(shù),可以使用bind方法消除最深層的嵌套回調(diào)函數(shù)。
db.lookupAsync("url", downloadURL); function downloadURL(url) { downloadAsync(url, showContents.bind(null, url)); // => window.showContents(url) = function(url, text) { ... } } function showContents(url, text) { console.log("contents of " + url + ": " + text); }
這種做法導(dǎo)致了代碼看起來(lái)根據(jù)順序性,但需要為操作序列的每個(gè)中間步驟命名,并且一步步地使用綁定,這可能導(dǎo)致尷尬的情況。
更勝一籌的方法是使用一個(gè)額外的抽象來(lái)簡(jiǎn)化。
function downloadFiles(url, file) { downloadAllAsync(["a.txt", "b.txt", "c.txt"], function(all) { var a = all[0], b = all[1], c = all[2]; // ... }); }
提示:
使用嵌套或命名的回調(diào)函數(shù)按順序地執(zhí)行多個(gè)異步操作。
嘗試在過(guò)多的嵌套的回調(diào)函數(shù)和尷尬的命名的非嵌套回調(diào)函數(shù)之間取得平衡。
避免將可被并行執(zhí)行的操作順序化。
當(dāng)心丟棄錯(cuò)誤對(duì)于同步的代碼,通過(guò)使用try語(yǔ)句塊包裝一段代碼很容易一下子處理所有的錯(cuò)誤。
try { f(); g(); h(); } catch (e) { // handle any error that occurred... }
異步的API傾向于將錯(cuò)誤表示為回調(diào)函數(shù)的特定參數(shù),或使用一個(gè)附加的錯(cuò)誤處理回調(diào)函數(shù)(有時(shí)被稱為errbacks)。
downloadAsync("a.txt", function(a) { downloadAsync("b.txt", function(b) { downloadAsync("c.txt", function(c) { console.log("Content: " + a + b + c); }, function(error) { console.log("Error: " + error); }) }, function(error) { // repeated error-handling logic console.log("Error: " + error); }) }, function(error) { // repeated error-handling logic console.log("Error: " + error); })
上述代碼中,每一步的處理都使用了相同的錯(cuò)誤處理邏輯,我們可以在一個(gè)共享的作用域中定義一個(gè)錯(cuò)誤處理的函數(shù),將重復(fù)代碼抽象出來(lái)。
function onError(error) { console.log("Error: " + error); } downloadAsync("a.txt", function(a) { downloadAsync("b.txt", function(b) { downloadAsync("c.txt", function(c) { console.log("Content: " + a + b + c); }, onError) }, onError) }, onError)
另一種錯(cuò)誤處理API的風(fēng)格只需要一個(gè)回調(diào)函數(shù),該回調(diào)函數(shù)的第一個(gè)參數(shù)如果有錯(cuò)誤發(fā)生那就表示為一個(gè)錯(cuò)誤,否則就位一個(gè)假值,比如null。
function onError(error) { console.log("Error: " + error); } downloadAsync("a.txt", function(error, a) { if (error) return onError(error); downloadAsync("b.txt", function(error, b) { if (error) return onError(error); downloadAsync(url13, function(error, c) { if (error) return onError(error); console.log("Content: " + a + b + c); }); }); });
提示:
通過(guò)編寫(xiě)共享的錯(cuò)誤處理函數(shù)來(lái)避免復(fù)制和粘貼錯(cuò)誤處理代碼。
確保明確地處理所有的錯(cuò)誤條件以避免丟棄錯(cuò)誤。
對(duì)異步循環(huán)使用遞歸設(shè)想有一個(gè)函數(shù)接收一個(gè)URL的數(shù)組并嘗試依次下載每個(gè)文件。
function downloadOneSync(urls) { for (var i = 0, n = urls.length; i < n; i++) { downloadAsync(urls[i], onsuccess, function(error) { // ? }); // loop continues } throw new Error("all downloads failed"); }
上述代碼將啟動(dòng)所有的下載,而不是等待一個(gè)完成再試下一個(gè)。
解決方案是將循環(huán)實(shí)現(xiàn)為一個(gè)函數(shù),所以我們可以決定何時(shí)開(kāi)始每次迭代。
function downloadOneAsync(urls, onsuccess, onfailure) { var n = urls.length; function tryNextURL(i) { if (i >= n) { onfailure("all downloads failed"); return; } downloadAsync(urls[i], onsuccess, function() { tryNextURL(i + 1); }); } tryNextURL(0); }
局部函數(shù)tryNextURL是一個(gè)遞歸函數(shù)。它的實(shí)現(xiàn)調(diào)用了其自身。目前典型的JavaScript環(huán)境中一個(gè)遞歸函數(shù)同步調(diào)用自身過(guò)多次(例如10萬(wàn)次)會(huì)導(dǎo)致失敗。
JavaScript環(huán)境通常在內(nèi)存中保存一塊固定的區(qū)域,稱為調(diào)用棧,用于記錄函數(shù)調(diào)用返回前下一步該做什么。
function negative(x) { return abs(x) * -1; } function abs(x) { return Math.abs(x); } console.log(negative(42));
當(dāng)程序使用參數(shù)42調(diào)用Math.abs方法時(shí),有好幾個(gè)其他的函數(shù)調(diào)用也在進(jìn)行,每個(gè)都在等待另一個(gè)的調(diào)用返回。
最新的函數(shù)調(diào)用將信息推入棧(被表示為棧的最底層的幀),該信息也將首先從棧中彈出。當(dāng)Math.abs執(zhí)行完畢,將會(huì)返回給abs函數(shù),其將返回給negative函數(shù),然后將返回到最外面的腳本。
當(dāng)一個(gè)程序執(zhí)行中有太多的函數(shù)調(diào)用,它會(huì)耗盡??臻g,最終拋出異常,這種情況被稱為棧溢出。
downloadOneAsync函數(shù),不是直到遞歸調(diào)用返回后才被返回,downloadOneAsync只在異步回調(diào)函數(shù)中調(diào)用自身。記住異步API在其回調(diào)函數(shù)被調(diào)用前會(huì)立即返回。所以downloadOneAsync返回,導(dǎo)致其棧幀在任何遞歸調(diào)用將新的棧幀推入棧前,會(huì)從調(diào)用棧中彈出。事實(shí)上,回調(diào)函數(shù)總在事件循環(huán)的多帶帶輪次中被調(diào)用,事件循環(huán)的每個(gè)輪次中調(diào)用其事件處理程序的調(diào)用棧最初是空的。所以無(wú)論downloadOneAsync需要多少次迭代,都不會(huì)耗盡??臻g。
提示:
循環(huán)不能是異步的。
使用遞歸函數(shù)在事件循環(huán)的多帶帶輪次中執(zhí)行迭代。
在事件循環(huán)的多帶帶輪次中執(zhí)行遞歸,并不會(huì)導(dǎo)致調(diào)用棧溢出。
不要在計(jì)算時(shí)阻塞事件隊(duì)列如果你的應(yīng)用程序需要執(zhí)行代價(jià)高昂的算法你該怎么辦呢?
也許最簡(jiǎn)單的方法是使用像Web客戶端平臺(tái)的Worker API這樣的并發(fā)機(jī)制。
但是不是所有的JavaScript平臺(tái)都提供了類似Worker這樣的API,另一種方法是算法分解為多個(gè)步驟,每個(gè)步驟組成一個(gè)可管理的工作塊。
Member.prototype.inNetwork = function(other){ var visited = {}, worklist = [this]; while (worklist.length > 0) { var member = worklist.pop(); // ... if (member === other) { // found? return true; } // ... } return false; };
如果這段程序核心的while循環(huán)代價(jià)太過(guò)高昂,搜索工作很可能會(huì)以不可接受的時(shí)間運(yùn)行而阻塞應(yīng)用程序事件隊(duì)列。
幸運(yùn)的是,這種算法被定義為一個(gè)步驟集的序列——while循環(huán)的迭代。我們可以通過(guò)增加一個(gè)回調(diào)參數(shù)將inNetwork轉(zhuǎn)換為一個(gè)匿名函數(shù),將while循環(huán)替換為一個(gè)匿名的遞歸函數(shù)。
Member.prototype.inNetwork = function(other, callback) { var visited = {}, worklist = [this]; function next() { if (worklist.length === 0) { callback(false); return; } var number = worklist.pop(); // ... if (member === other) { // found? callback(true); return; } // ... setTimeout(next, 0); // schedule the next iteration } setTimeout(next, 0); // schedule the next iteration };
局部的next函數(shù)執(zhí)行循環(huán)中的單個(gè)迭代然后調(diào)度應(yīng)用程序事件隊(duì)列來(lái)異步運(yùn)行下一次迭代。這使得在此期間已經(jīng)發(fā)生的其他事件被處理后才繼續(xù)下一次迭代。當(dāng)搜索完成后,通過(guò)找到一個(gè)匹配或遍歷完整個(gè)工作表,我們使用結(jié)果值調(diào)用回調(diào)函數(shù)并通過(guò)調(diào)用沒(méi)有調(diào)度任何迭代的next來(lái)返回,從而有效地完成循環(huán)。
要調(diào)度迭代,我們使用多數(shù)JavaScript平臺(tái)都可用的、通用的setTimeout API來(lái)注冊(cè)next函數(shù),是next函數(shù)經(jīng)過(guò)一段最少時(shí)間(0毫秒)后運(yùn)行。這具有幾乎立刻將回調(diào)函數(shù)添加到事件隊(duì)列上的作用。
提示:
避免在主事件隊(duì)列中執(zhí)行代價(jià)高昂的算法。
在支持Worker API的平臺(tái),該API可以用來(lái)在一個(gè)獨(dú)立的事件隊(duì)列中運(yùn)行長(zhǎng)計(jì)算程序。
在Worker API不可用或代價(jià)昂貴的環(huán)境中,考慮將計(jì)算程序分解到事件循環(huán)的多個(gè)輪次中。
使用計(jì)數(shù)器來(lái)執(zhí)行并行操作function downloadAllAsync(urls, onsuccess, onerror) { var result = [], length = urls.length; if (length === 0) { setTimeout(onsuccess.bind(null, result), 0); return; } urls.forEach(function(url) { downloadAsync(url, function(text) { if (result) { // race condition reuslt.push(text); if (result.length === urls.length) { onsuccess(result); } } }, function(error) { if (result) { result = null; onerror(error); } }); }); }
上述代碼有錯(cuò)誤。
當(dāng)一個(gè)應(yīng)用程序依賴于特定的事件順序才能正常工作時(shí),這個(gè)程序會(huì)遭受數(shù)據(jù)競(jìng)爭(zhēng)(data race)。數(shù)據(jù)競(jìng)爭(zhēng)是指多個(gè)并發(fā)操作可以修改共享的數(shù)據(jù)結(jié)構(gòu),這取決于它們發(fā)生的順序。
var filenames = [ "huge.txt", "tiny.txt", "medium.txt" ]; downloadAllAsync(filenames, function(files) { console.log("Huge file: " + files[0].length); // tiny console.log("Tiny file: " + files[1].length); // medium console.log("Medium file: " + files[2].length); // huge }, function(error) { console.log("Error: " + error); });
由于這些文件是并行下載的,事件可以以任意的順序發(fā)生。例如,如果tiny.txt先下載完成,接下來(lái)是medium.txt文件,最后是huge.txt文件,則注冊(cè)到downloadAllAsync的回調(diào)函數(shù)并不會(huì)按照它們被創(chuàng)建的順序進(jìn)行調(diào)用。但downloadAllAsync的實(shí)現(xiàn)是一旦下載完成就立即將中間結(jié)果保存在result數(shù)組的末尾。所以downloadAllAsync函數(shù)提供的保存下載文件內(nèi)容的數(shù)組的順序是未知的。
下面的方式可以實(shí)現(xiàn)downloadAllAsync不依賴不可預(yù)期的事件執(zhí)行順序而總能提供預(yù)期結(jié)果。我們不將每個(gè)結(jié)果放置到數(shù)組末尾,而是存儲(chǔ)在其原始的索引位置中。
function downloadAsync(urls, onsuccess, onerror) { var length = urls.length, result = []; if (length === 0) { setTimeout(onsuccess.bind(null, result), 0); return; } urls.forEach(function(url, i) { downloadAsync(url, function(text) { if (result) { result[i] = text; // store at fixed index // race condition if (result.length === urls.length) { onsuccess(result); } } }, function(error) { if (result) { result = null; onerror(error); } }); }); }
上述代碼依然是不正確的。
假如我們有如下的一個(gè)請(qǐng)求。
downloadAllAsync(["huge.txt", "medium.txt", "tiny.txt"]);
根據(jù)數(shù)組更新的契約,即設(shè)置一個(gè)索引屬性,總是確保數(shù)組的length屬性值大于索引。
如果tiny.txt文件最先被下載,結(jié)果數(shù)組將獲取索引未2的屬性,這將導(dǎo)致result.length被更新為3。用戶的success回調(diào)函數(shù)被過(guò)早地調(diào)用,其參數(shù)為一個(gè)不完整的結(jié)果數(shù)組。
正確地實(shí)現(xiàn)應(yīng)該是使用一個(gè)計(jì)數(shù)器來(lái)追蹤正在進(jìn)行的操作數(shù)量。
function downloadAsync(urls, onsuccess, onerror) { var pending = urls.length, result = []; if (pending === 0) { setTimeout(onsuccess.bind(null, result), 0); return; } urls.forEach(function(url, i) { downloadAsync(url, function(text) { if (result) { result[i] = text; // store at fixed index pending--; // register the success // race condition if (pedding === 0) { onsuccess(result); } } }, function(error) { if (result) { result = null; onerror(error); } }); }); }
提示:
JavaScript應(yīng)用程序中的事件發(fā)生是不確定的,即順序是不可預(yù)測(cè)的。
使用計(jì)數(shù)器避免并行操作中的數(shù)據(jù)競(jìng)爭(zhēng)。
絕不要同步地調(diào)用異步的回調(diào)函數(shù)設(shè)想有downloadAsync函數(shù)的一個(gè)變種,它持有一個(gè)緩存來(lái)避免多次下載同一個(gè)文件。
var cache = new Dict(); function downloadCachingAsync(url, onsuccess, onerror) { if (cache.has(url)) { onsuccess(cache.get(url)); // synchronous call return; } return downloadAsync(url, function(file) { cache.set(url, file); onsuccess(file); }, onerror); };
通常情況下,如果可以,它似乎會(huì)立即提供數(shù)據(jù),但這以微妙的方式違反了異步API客戶端的期望。
首先,它改變了操作的預(yù)期順序。
downloadCachingAsync("file.txt", function(file) { console.log("finished"); // might happen first }); console.log("starting");
其次,異步API的目的是維持事件循環(huán)中每輪的嚴(yán)格分離。這簡(jiǎn)化了并發(fā),通過(guò)減輕每輪事件循環(huán)的代碼量而不必?fù)?dān)心其他代碼并發(fā)地修改共享的數(shù)據(jù)結(jié)構(gòu)。同步地調(diào)用異步的回調(diào)函數(shù)違反了這一分離,導(dǎo)致在當(dāng)前輪完成之前,代碼用于執(zhí)行一輪隔離的事件循環(huán)。
downloadCachingAsync(remaining[0], function(file) { remaing.shift(); // ... }); status.display("Downloading " + remaining[0] + "...");
如果同步地調(diào)用該回調(diào)函數(shù),那么將顯示錯(cuò)誤的文件名的消息(或者更糟糕的是,如果隊(duì)列為空會(huì)顯示undefined)。
同步地調(diào)用異步的回調(diào)函數(shù)甚至可能會(huì)導(dǎo)致一些微妙的問(wèn)題。
異步的回調(diào)函數(shù)本質(zhì)上是以空的調(diào)用棧來(lái)調(diào)用,因此將異步的循環(huán)實(shí)現(xiàn)為遞歸函數(shù)是安全的,完全沒(méi)有累計(jì)超越調(diào)用??臻g的危險(xiǎn)。同步的調(diào)用不能保障這一點(diǎn),因而使得一個(gè)表面上的異步循環(huán)很可能會(huì)耗盡調(diào)用棧空間。
另一個(gè)問(wèn)題是異常。對(duì)于上面的downloadCachingAsync實(shí)現(xiàn),如果回調(diào)函數(shù)拋出一個(gè)異常,它將會(huì)在每輪的事件循環(huán)中,也就是開(kāi)始下載時(shí)而不是期望的一個(gè)分離的回合拋出該異常。
為了確??偸钱惒降卣{(diào)用回調(diào)函數(shù),我們可以使用通用的庫(kù)函數(shù)setTimeout在每隔一個(gè)最小的時(shí)間的超時(shí)時(shí)間后給事件隊(duì)列增加一個(gè)回調(diào)函數(shù)。
var cache = new Dict(); function downloadCachingAsync(url, onsuccess, onerror) { if (cache.has(url)) { var cached = cache.get(url); setTimeout(onsuccess.bind(null, cached), 0); return; } return downloadAsync(url, function(file) { cache.set(url, file); onsuccess(file); }, onerror); };
提示:
即使可以立即得到數(shù)據(jù),也絕不要同步地調(diào)用異步回調(diào)函數(shù)。
同步地調(diào)用異步的回調(diào)函數(shù)擾亂了預(yù)期的操作序列,并可能導(dǎo)致意想不到的交錯(cuò)代碼。
同步地調(diào)用異步的回調(diào)函數(shù)可能導(dǎo)致棧溢出或錯(cuò)誤地處理異常。
使用異步的API,比如setTimeout函數(shù)來(lái)調(diào)度異步回調(diào)函數(shù),使其運(yùn)行于另一個(gè)回合。
使用promise模式清潔異步邏輯構(gòu)建異步API的一種流行的替代方式是使用promise(有時(shí)也被稱為deferred或future)模式。
基于promise的API不接收回調(diào)函數(shù)作為參數(shù),相反,它返回一個(gè)promise對(duì)象,該對(duì)象通過(guò)其自身的then方法接收回調(diào)函數(shù)。
var p = downloadP("file.txt"); p.then(function(file) { console.log("file: " + file); });
promise的力量在于它們的組合性。傳遞給then的回調(diào)函數(shù)不僅產(chǎn)生影響,也可以產(chǎn)生結(jié)果。通過(guò)回調(diào)函數(shù)返回一個(gè)值,可以構(gòu)造一個(gè)新的promise。
var fileP = downloadP("file.txt"); var lengthP = fileP.then(function(file) { return file.length; }); lengthP.then(function(length) { console.log("length: " + length); });
promise可以非常容易地構(gòu)造一個(gè)實(shí)用程序來(lái)拼接多個(gè)promise的結(jié)果。
var filesP = join(downloadP("file1.txt"), downloadP("file2.txt"), downloadP("file3.txt")); filesP.then(function(files) { console.log("file1: " + files[0]); console.log("file2: " + files[1]); console.log("file3: " + files[2]); });
promise庫(kù)也經(jīng)常提供一個(gè)叫做when的工具函數(shù)。
var fileP1 = downloadP("file1.txt"), fileP2 = downloadP("file2.txt"), fileP3 = downloadP("file3.txt"); when([fileP1, fileP2, fileP3], function(files) { console.log("file1: " + files[0]); console.log("file2: " + files[1]); console.log("file3: " + files[2]); });
使promise成為卓越的抽象層級(jí)的部分原因是通過(guò)then方法的返回值來(lái)聯(lián)系結(jié)果,或者通過(guò)工具函數(shù)如join來(lái)構(gòu)成promise,而不是在并行的回調(diào)函數(shù)間共享數(shù)據(jù)結(jié)構(gòu)。這本質(zhì)上是安全的,因?yàn)樗苊饬藬?shù)據(jù)競(jìng)爭(zhēng)。
有時(shí)故意創(chuàng)建某種類的數(shù)據(jù)競(jìng)爭(zhēng)是有用的。promise為此提供了一個(gè)很好的機(jī)制。例如,一個(gè)應(yīng)用程序可能需要嘗試從多個(gè)不同的服務(wù)器上同時(shí)下載同一份文件,而選擇最先完成的那個(gè)文件。
var fileP = select(downloadP("http://example1.com/file.txt"), downloadP("http://example1.com/file.txt"), downloadP("http://example1.com/file.txt")); fileP.then(function(file) { console.log("file: " + file); });
select函數(shù)的另一個(gè)用途是提供超時(shí)來(lái)終止長(zhǎng)時(shí)間的操作。
var fileP = select(downloadP("file.txt"), timeoutErrorP(2000)); fileP.then(function(file) { console.log("file: " + file); }, function(error) { console.log("I/O error or timeout: " + error); });
提示:
promise代表最終值,即并行完成時(shí)最終產(chǎn)生的結(jié)果。
使用promise組合不同的并行操作。
使用promise模式的API避免數(shù)據(jù)競(jìng)爭(zhēng)。
在要求有意的競(jìng)爭(zhēng)條件時(shí)使用select(也被稱為choose)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/94958.html
摘要:前端每周清單第期現(xiàn)狀分析與優(yōu)化策略單元測(cè)試爬蟲(chóng)作者王下邀月熊編輯徐川前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開(kāi)發(fā)者了解一周前端熱點(diǎn)分為新聞熱點(diǎn)開(kāi)發(fā)教程工程實(shí)踐深度閱讀開(kāi)源項(xiàng)目巔峰人生等欄目。 showImg(https://segmentfault.com/img/remote/1460000011008022); 前端每周清單第 29 期:Web 現(xiàn)狀分析與優(yōu)化策略...
摘要:前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開(kāi)發(fā)者了解一周前端熱點(diǎn)分為新聞熱點(diǎn)開(kāi)發(fā)教程工程實(shí)踐深度閱讀開(kāi)源項(xiàng)目巔峰人生等欄目。對(duì)該漏洞的綜合評(píng)級(jí)為高危。目前,相關(guān)利用方式已經(jīng)在互聯(lián)網(wǎng)上公開(kāi),近期出現(xiàn)攻擊嘗試爆發(fā)的可能。 前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開(kāi)發(fā)者了解一周前端熱點(diǎn);分為新聞熱點(diǎn)、開(kāi)發(fā)教程、工程實(shí)踐、深度閱讀、開(kāi)源項(xiàng)目、巔峰人生等欄目。歡...
摘要:但實(shí)例化該構(gòu)造函數(shù)仍然得到的是的實(shí)例?;蛘撸瑸榱吮苊庠谒胁檎覍傩缘牡胤蕉疾迦脒@段樣本代碼,我們可以將該模式抽象到的構(gòu)造函數(shù)中。該構(gòu)造函數(shù)封裝了所有在單一數(shù)據(jù)類型定義中編寫(xiě)健壯字典的技術(shù)細(xì)節(jié)。 參考書(shū)籍:《Effective JavaScript》 數(shù)組和字典 對(duì)象是JavaScript中最萬(wàn)能的數(shù)據(jù)結(jié)構(gòu)。取決于不同的環(huán)境,對(duì)象可以表示一個(gè)靈活的鍵值關(guān)聯(lián)記錄,一個(gè)繼承了方法的面向?qū)ο髷?shù)...
閱讀 1448·2023-04-25 19:51
閱讀 1936·2019-08-30 15:55
閱讀 1748·2019-08-30 15:44
閱讀 2707·2019-08-30 13:58
閱讀 2702·2019-08-29 16:37
閱讀 1082·2019-08-29 15:34
閱讀 4017·2019-08-29 11:05
閱讀 2633·2019-08-28 17:51