[Effective JavaScript 筆記]第66條:使用計數器來執行並行操作

来源:http://www.cnblogs.com/wengxuesong/archive/2016/07/29/5718098.html
-Advertisement-
Play Games

第63條建議使用工具函數downloadAllAsync接收一個URL數組並下載所有文件,結果返回一個存儲了文件內容的數組,每個URL對應一個字元串。downloadAllAsync並不只有清理嵌套回調函數的好處,其主要好處是並行下載文件。我們可以在同一個事件迴圈中一次啟動所有文件的下載,而不用等待... ...


第63條建議使用工具函數downloadAllAsync接收一個URL數組並下載所有文件,結果返回一個存儲了文件內容的數組,每個URL對應一個字元串。downloadAllAsync並不只有清理嵌套回調函數的好處,其主要好處是並行下載文件。我們可以在同一個事件迴圈中一次啟動所有文件的下載,而不用等待每個文件完成下載。
並行邏輯是微妙的,很容易出錯。下麵有實現有一個隱藏的缺陷。

function downloadAllAsync(urls,onsuccess,onerror){
  var result=[],length=urls.length;
  if(length === 0){
    setTimeout(onsuccess.bind(null,result),0);
  }
  urls.forEach(function(url){
    downloadAsync(url,function(text){
      if(result){
        reslut.push(text);
        if(result.length===urls.length){
          onsuccess(result);
        }
      }
    },function(error){
      if(result){
        result=null;
        onerror(error);
      }
    });
  });
}

這個函數有嚴重的錯誤,但首先讓我們看看它是如何工作的。先確保如果數組是空的,則會使用空結果數組調用回調函數。如果不這樣做,這兩個回調函數將不會被調用,因為forEach迴圈是空的。接下來,遍歷整個URL數組,為每個URL請求一個非同步下載。每次下載成功,就將文件內容加入到result數組中。如果所有URL都被成功下載,使用result數組調用onsuccess回調函數。如果有任何失敗的下載,使用錯誤值調用onerror回調函數。如果有多個下載失敗,設置result數組為null,從而保證onerror只被調用一次,即在第一次錯誤發生時。
錯誤示例

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);
});

由於這些文件是並行下載的,事件可以以任意的順序發生(因些被添加到應用程式事件序列)。例如,如果tiny.txt先下載完成,接下來是medium.txt文件,最後是buge.txt文件,則註冊到downloadAllAsync的回調函數並不會按照它們被創建的順序進行調用。但downloadAllAsync的實現是一旦下載完成就立即將中間結果保存在result數組的末尾。所以downloadAllAsync函數提供的保存下載文件內容的數組的順序是未知的。這個API幾乎不可用,因為無法確認哪個結果對應哪個文件。
程式的執行順序不能保證與事件發生的順序一致。
當一個應用程式依賴於特定的事件順序才能正常工作時,這個程式會遭受數據競爭。數據競爭是指多個併發操作可以修改共用的數據結構,這取決於它們發生的順序。數據競爭是真正棘手的錯誤。它們可能不會出現於特定的測試中,因為運行相同的程式兩次,每次可能會得不到不同的結果。例如downloadAllAsync的使用者可能會對文件重新排序,基於的順序是哪個文件可能會最先完成下載。

downloadAllAsync(filenames,function(files){
  console.log('Huge file:'+files[2].length);
  console.log('Tiny file:'+files[0].length);
  console.log('Medium file:'+files[1].length);
},function(error){
  console.log('Error: '+error);
});

在這種情況下大多數時候結果是相同的順序,但偶爾由於改變了伺服器負載均衡或網路緩存,文件可能不是期望的順序。我們可以順序下載文件,但也失去了併發的性能優勢。
下麵實現downloadAllAsync不依賴不可預期的事件執行順序而總能提供預期結果。我們不將每個結果放置到數組末尾,而是存儲在其原始的索引位置。

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){
        reslut[i]=text;
        if(result.length===urls.length){
          onsuccess(result);
        }
      }
    },function(error){
      if(result){
        result=null;
        onerror(error);
      }
    });
  });
}

該實現利用了forEach回調函數的第二個參數。第二個參數為當前迭代提供了數組索引。這也不正確。第51條描述數組更新的契約,即設置一個索引屬性,總是確保數組的length屬性值大於索引。假設有如下的一個請求。

downloadAllAsync(['huge.txt','medium.txt','tiny.txt']);

如果tiny.txt文件最先被下載,結果數組將獲取索引為2的屬性,這將導致result.length被更新為3。用戶的success回調函數將被過早地調用,其參數為一個不完整的結果數組。
正確的實現應該是使用一個計數器來追蹤正在進行的操作數量。

function downloadAllAsync(urls,onsuccess,onerror){
  var pending=urls.length;
  var result=[];
  if(pending === 0){
    setTimeout(onsuccess.bind(null,result),0);
    return;
  }
  urls.forEach(function(url){
    downloadAsync(url,function(text){
      if(result){
        reslut[i]=text;
        pending--;
        if(pending===0){
          onsuccess(result);
        }
      }
    },function(error){
      if(result){
        result=null;
        onerror(error);
      }
    });
  });
}

現在不論事件以什麼樣的順序發生,pending計數器都能準確地指出何時所有的事件會被完成,並以適當的順序返回完整的結果。

提示

  • js應用程式中的事件發生是不確定的,即順序是不可預測的

  • 使用計數器避免並行操作中的數據競爭


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • css的書寫格式一共有三種 行內樣式:意思是在行內中寫樣式 例如說<p style="color:red">用行內樣式編寫我的顏色</p> 只適用於<body>(字體顏色和背景顏色)和<body>裡面的標簽,但不適用於<body>之外的例如:head,title,之類的標簽 內嵌樣式:使用style ...
  • 眾所周知,一個元素,動往往比靜更吸引眼球; 一套操作界面,合適的動態交互反饋能給用戶帶來更好的操作體驗; 一個H5運營宣傳頁,炫酷的動畫特效定能助力傳播和品牌打造。 近兩年,小到loading動畫,表單動效,大到各式各樣H5運營頁的炫酷展現,“動效設計”一詞可謂是火遍大江南北,而動效設計早已成為一名 ...
  • 宋體 SimSun 黑體 SimHei 微軟雅黑 Microsoft YaHei 微軟正黑體 Microsoft JhengHei 新宋體 NSimSun 新細明體 PMingLiU 細明體 MingLiU 標楷體 DFKai-SB 仿宋 FangSong 楷體 KaiTi 仿宋_GB2312 Fa ...
  • js 中的奇葩問題 1、eg:000101 = 65; 000101為獲取到的字元串類型,通過click事件傳遞,000101變為了65; 解決方法:jq中直接運行的代碼中獲取到的000101為字元串類型,但是後邊人為運行此代碼相關的代碼時獲取到的000101就為number類型。由此可見在JS中數 ...
  • colspan和rowspan這兩個屬性用於創建特殊的表格。 colspan是“column span(跨列)”的縮寫。colspan屬性用在td標簽中,用來指定單元格橫向跨越的列數: 在瀏覽器中將顯示如下: 單元格1 單元格2 單元格3 單元格4 該例通過把colspan設為“3”, 令所在單元格 ...
  • 1.事件對象: 在觸發DOM事件的時候都會產生一個對象。 2.事件對象event: (1)、type:獲取事件類型 (2)、target:獲取事件目標 (3)、stopPropagation():阻止事件冒泡 (4)、preventDefault():阻止事件預設行為 ~~~~~~~~~~~~~~~ ...
  • ajax 非同步請求成功後需要新開視窗打開 url,使用的是 window.open() 方法,但是很可惜被瀏覽器給攔截了,怎麼解決這個問題呢 ajax 非同步請求成功後需要新開視窗打開 url,使用的是 window.open() 方法,但是很可惜被瀏覽器給攔截了,怎麼解決這個問題呢 問題: 前面開發 ...
  • DOM有三種節點:元素節點、屬性節點、文本節點。 一、用nodeType可以檢測節點的類型 這樣方便在js中對各個節點進行操作。 元素節點:html中的標簽。 屬性節點:html便簽中的屬性值。 文本節點:元素節點之間的文本。 二、用body的childNodes來測試 來看body的childNo ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...