JavaScript で大きなファイルの並列ダウンロードを実装する方法

JavaScript で大きなファイルの並列ダウンロードを実装する方法

大きなファイルをアップロードする場合の解決策はすでにご存知の方もいらっしゃると思います。大きなファイルをアップロードする場合、アップロードの効率を上げるために、通常は Blob.slice メソッドを使用して、指定されたサイズに応じて大きなファイルを分割し、マルチスレッドを開始してブロックでアップロードします。すべてのブロックが正常にアップロードされたら、サーバーにブロックを結合するように通知します。

では、大きなファイルのダウンロードにも同様の考え方を採用できるでしょうか?サーバーが Range リクエスト ヘッダーをサポートしている場合は、次の図に示すように、マルチスレッド ブロック ダウンロードを実装することもできます。

上の図を読んだ後、大きなファイルをダウンロードするための解決策についてある程度理解できたと思います。次に、HTTP 範囲リクエストを紹介します。

1. HTTP範囲リクエスト

HTTP プロトコル範囲要求により、サーバーは HTTP メッセージの一部のみをクライアントに送信できます。範囲要求は、大きなメディア ファイルを転送する場合や、ファイル ダウンロードの再開機能と組み合わせて使用​​する場合に便利です。応答に Accept-Ranges ヘッダーが存在する場合 (その値が「none」ではない場合)、サーバーが範囲要求をサポートしていることを示します。

Range ヘッダーでは、一度に複数の部分を要求することができ、サーバーはそれらをマルチパート ファイルの形式で返します。サーバーが範囲応答を返す場合は、206 部分コンテンツ ステータス コードを使用する必要があります。要求された範囲が無効な場合、サーバーはクライアント エラーを示す 416 Range Not Satisfiable ステータス コードを返します。サーバーは Range ヘッダーを無視し、ステータス コード 200 でファイル全体を返すことができます。

1.1 範囲構文

範囲: <単位>=<範囲開始>-
範囲: <単位>=<範囲開始>-<範囲終了>
範囲: <単位>=<範囲開始>-<範囲終了>、<範囲開始>-<範囲終了>
範囲: <単位>=<範囲開始>-<範囲終了>、<範囲開始>-<範囲終了>、<範囲開始>-<範囲終了>
  • 単位: 範囲要求に使用される単位。通常はバイトです。
  • <range-start>: 特定の単位で範囲の開始値を示す整数。
  • <range-end>: 特定の単位で範囲の終了値を示す整数。この値はオプションです。指定しない場合は、範囲がドキュメントの最後まで拡張されることを意味します。

Range 構文を理解した後、実際の使用例を見てみましょう。

1.1.1 単一カテゴリ

$ curl http://i.imgur.com/z4d4kWk.jpg -i -H "範囲: バイト=0-1023"

1.1.2 複数の範囲

$ curl http://www.example.com -i -H "範囲: バイト=0-50、100-150"

さて、HTTP 範囲リクエストに関する知識はこれですべてです。それでは、本題に入り、大きなファイルをダウンロードする方法を紹介していきましょう。

2. 大きなファイルをダウンロードする方法

以下の内容をよりよく理解できるように、全体的なフローチャートを見てみましょう。

大きなファイルのダウンロードのプロセスを理解した後、まず上記のプロセスに関係するいくつかの補助関数を定義しましょう。

2.1 補助関数の定義

2.1.1 getContentLength関数を定義する

名前が示すように、getContentLength 関数はファイルの長さを取得するために使用されます。この関数では、HEAD リクエストを送信し、応答ヘッダーから Content-Length 情報を読み取って、現在の URL に対応するファイルのコンテンツの長さを取得します。

関数 getContentLength(url) {
  新しい Promise を返します ((resolve, reject) => {
    xhr = new XMLHttpRequest();
    xhr.open("HEAD", url);
    xhr.send();
    xhr.onload = 関数 () {
      解決する(
        ~~xhr.getResponseHeader("コンテンツの長さ")
       );
    };
    xhr.onerror = 拒否;
  });
}

2.1.2 asyncPool 関数を定義する JavaScript で同時実行制御を実装するにはどうすればよいですか? この記事では、非同期タスクの同時実行制御を実装するために使用される asyncPool 関数を紹介しました。この関数は 3 つのパラメータを受け取ります。

  • poolLimit (数値型): 制限する同時接続数を示します。
  • 配列(配列型):タスク配列を表します。
  • iteratorFn (関数型): 各タスク項目を処理するために使用される反復関数を表します。この関数は、Promise オブジェクトまたは非同期関数を返します。
非同期関数 asyncPool(poolLimit, 配列, iteratorFn) {
  const ret = []; // すべての非同期タスクを保存 const performing = []; // 実行中の非同期タスクを保存 for (const item of array) {
    // iteratorFn 関数を呼び出して非同期タスクを作成します const p = Promise.resolve().then(() => iteratorFn(item, array));
    ret.push(p); // 新しい非同期タスクを保存 // poolLimit 値がタスクの総数以下の場合は、同時実行制御を実行します if (poolLimit <= array.length) {
      // タスクが完了したら、実行中のタスクの配列から完了したタスクを削除します。const e = p.then(() => execute.splice(executing.indexOf(e), 1));
      実行中の非同期タスクを保存する if (executing.length >= poolLimit) {
        await Promise.race(executing); // より高速なタスクが完了するまで待機します }
    }
  }
  Promise.all(ret) を返します。
}

2.1.3 getBinaryContent関数を定義する
getBinaryContent 関数は、渡されたパラメータに基づいて範囲要求を開始し、指定された範囲内のファイル データ ブロックをダウンロードするために使用されます。

関数 getBinaryContent(url, 開始, 終了, i) {
  新しい Promise を返します ((resolve, reject) => {
    試す {
      xhr = new XMLHttpRequest();
      xhr.open("GET", url, true);
      xhr.setRequestHeader("range", `bytes=${start}-${end}`); // リクエスト ヘッダーに範囲リクエスト情報を設定します xhr.responseType = "arraybuffer"; // 戻り値の型を arraybuffer に設定します
      xhr.onload = 関数 () {
        解決する({
          index: i, // ファイル ブロックのインデックス buffer: xhr.response, // 範囲要求に対応するデータ });
      };
      xhr.send();
    } キャッチ (エラー) {
      拒否(新しいエラー(err));
    }
  });
}

ArrayBuffer オブジェクトは、汎用の固定長の生のバイナリ データ バッファーを表すために使用されることに注意してください。 ArrayBuffer の内容を直接操作することはできませんが、型付き配列オブジェクトまたは DataView オブジェクトを介して操作することができます。これらのオブジェクトは、バッファー内のデータを特定の形式で表し、これらの形式を使用してバッファーの内容を読み書きします。

2.1.4 連結関数を定​​義する ArrayBuffer オブジェクトを直接操作することはできないため、まず ArrayBuffer オブジェクトを Uint8Array オブジェクトに変換してから連結操作を実行する必要があります。以下に定義される連結関数は、ダウンロードされたファイル データ ブロックを結合するために使用されます。具体的なコードは次のとおりです。

関数連結(配列) {
  (!arrays.length) の場合は null を返します。
  totalLength = arrays.reduce((acc, value) => acc + value.length, 0); とします。
  結果を新しいUint8Array(totalLength)とします。
  長さを 0 にします。
  for (let 配列の配列) {
    結果を設定します(配列、長さ);
    長さ += 配列.長さ;
  }
  結果を返します。
}

2.1.5 saveAs関数の定義
saveAs 関数は、クライアントのファイル保存機能を実装するために使用されます。ここでは単純な実装を示します。実際のプロジェクトでは、FileSaver.js を直接使用することを検討してください。

関数 saveAs({ name, buffers, mime = "application/octet-stream" }) {
  const blob = new Blob([バッファ], { type: mime });
  const blobUrl = URL.createObjectURL(blob);
  定数a = document.createElement("a");
  a.download = 名前 || Math.random();
  .href = blobUrl;
  クリック();
  URL.revokeObjectURL(blob);
}

saveAs 関数では、Blob と Object URL を使用しました。オブジェクト URL は、Blob および File オブジェクトを画像、ダウンロード可能なバイナリ データ リンクなどの URL ソースとして使用できるようにする疑似プロトコルです。ブラウザでは、URL.createObjectURL メソッドを使用してオブジェクト URL を作成します。このメソッドは、Blob オブジェクトを受け取り、blob:<origin>/<uuid> の形式でそのオブジェクトに固有の URL を作成します。対応する例は次のとおりです。

ブロブ:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641

ブラウザは、URL.createObjectURL によって生成された各 URL の URL → Blob マッピングを内部に保存します。したがって、このような URL は短くなりますが、BLOB にアクセスできます。生成された URL は、現在のドキュメントが開いている間のみ有効です。

さて、オブジェクト URL については以上です。

2.1.6 ダウンロード機能を定義する

ダウンロード関数はダウンロード操作を実装するために使用され、次の 3 つのパラメータをサポートします。

  • url (文字列型): 事前にダウンロードされたリソースのアドレス。
  • chunkSize (数値型): チャンクのサイズ (バイト単位)。
  • poolLimit (数値型): 同時リクエストの数を示します。
非同期関数ダウンロード({ url, chunkSize, poolLimit = 1 }) {
  const contentLength = getContentLength(url);
  const chunks = typeof chunkSize === "number" ? Math.ceil(contentLength / chunkSize) : 1;
  const 結果 = asyncPool( を待機します
    プール制限、
    [...新しい配列(チャンク).keys()],
    (i) => {
      start = i * chunkSize とします。
      end = i + 1 == chunks とします。contentLength - 1 : (i + 1) * chunkSize - 1;
      getBinaryContent(url, start, end, i) を返します。
    }
  );
  const sortedBuffers = 結果
    .map((item) => 新しい Uint8Array(item.buffer));
  連結した値を返します(sortedBuffers);
}

2.2 大容量ファイルのダウンロードの使用例

上記で定義した補助関数に基づいて、大きなファイルの並列ダウンロードを簡単に実装できます。具体的なコードは次のとおりです。

関数 multiThreadedDownload() {
  定数 url = document.querySelector("#fileUrl").value;
  if (!url || !/https?/.test(url)) 戻り値:
  console.log("マルチスレッドダウンロードが開始されました: " + +new Date());
  ダウンロード({
    URL、
    チャンクサイズ: 0.1 * 1024 * 1024,
    プール制限: 6,
  }).then((バッファ) => {
    console.log("マルチスレッドダウンロードが終了しました: " + +new Date());
    saveAs({ buffers, name: "My compressed package", mime: "application/zip" });
  });
}

完全なサンプルコードはかなり長いため、具体的なコードは掲載しません。ご興味がございましたら、以下のアドレスにアクセスしてサンプルコードを閲覧することができます。

完全なサンプルコード: https://gist.github.com/semlinker/837211c039e6311e1e7629e5ee5f0a42

ここでは、大きなファイルのダウンロード例の実行結果を見てみましょう。

結論

この記事では、JavaScript の async-pool ライブラリが提供する asyncPool 関数を使用して、大きなファイルの並列ダウンロードを実現する方法を紹介します。 Abaoge 氏は、asyncPool 関数の紹介に加えて、HEAD リクエストを通じてファイル サイズを取得する方法、HTTP 範囲リクエストを開始する方法、クライアントにファイルを保存する方法などの関連知識も紹介しました。実際、asyncPool 関数は、大きなファイルの並列ダウンロードだけでなく、大きなファイルの並列アップロードも実現できます。興味のある方は、自分で試してみてください。

上記は、JavaScript で大容量ファイルの並列ダウンロードを実装する方法の詳細です。JavaScript で大容量ファイルの並列ダウンロードの詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • js は複数のカウントダウンを並列に実現します js グループ カウントダウン
  • Chrome DevTools を使用して Node.js と JavaScript を直接デバッグする方法の詳細な説明 (並行して)
  • headjsはウェブサイトの並列読み込みとJSの順次実行を実装します
  • JavaScript における並列処理の深い理解
  • Javascript 並列コンピューティング実装コード
  • ファイルのダウンロードを実装するフロントエンド JavaScript の例
  • バックグラウンドからの Javascript フロントエンド ダウンロード ファイル ストリーム コード例
  • 複数ファイルをダウンロードするJavaScriptの手法の分析
  • ネイティブjsは、ファイルのアップロード、ダウンロード、パッケージ化などのサンプルメソッドを実装します。
  • ファイルのダウンロードと名前変更のための JavaScript コード例
  • Vue は PDF ファイルのオンライン プレビューとダウンロードを実装します (pdf.js)
  • JavaScript を使用してファイルを作成およびダウンロードする (クリックをシミュレートする)
  • node.js expressフレームワークを使用してファイルのアップロードとダウンロード機能を実装する詳細な例

<<:  MySQL 5.7.21 winx64 グリーンバージョンのインストールと設定方法のグラフィックチュートリアル

>>:  nginx ip ブラックリストの動的禁止の例

推薦する

MySQL クロステーブルクエリとクロステーブル更新

SQL の基礎知識がある友人は、「クロステーブル クエリ」について聞いたことがあるはずですが、クロス...

XHTML と CSS によるオブジェクト指向プログラミング

<br />XHTML と CSS がオブジェクト指向だったらよかったのに。 。太陽は北...

MySQL でよく使われる型変換関数の概要 (推奨)

1. Concat関数。よく使用される接続文字列: concat 関数。たとえば、SQLクエリ条件...

MYSQL 文字関数を使用してデータをフィルタリングすることに関する質問

問題の説明:構造:テストには2つのフィールドがあります。これらは col1 と col2 で、どちら...

Nginx転送マッチングルールの実装

1. 正規表現マッチング大文字と小文字を区別するマッチングの場合 ~ ~*は大文字と小文字を区別しな...

Node はあいまい検索用の検索ボックスを実装します

この記事の例では、検索ボックスでファジークエリを実装するためのNodeの具体的なコードを参考までに共...

フロントエンドのパフォーマンス最適化を学ぶ準備として、HTMLページのレンダリングプロセスを理解する

現在、フロントエンドのパフォーマンス最適化について学んでいます。適切な解決策を見つけ、パフォーマンス...

CSS で順序付きリスト項目と順序なしリスト項目のスタイルを設定する方法

順序なしリストでは、順序なしリストのシンボルは各リストの前に表示されるドットです。順序付きリスト o...

Vueソースコード解析における仮想DOMの詳しい説明

なぜ仮想DOMが必要なのでしょうか?仮想 DOM はブラウザのパフォーマンス問題を解決するために設計...

MySQL Innodbインデックスの原理の詳細な説明

導入振り返ってみると、4年前、私がMySQLのインデックスについて学んでいたとき、先生はインデックス...

Vue における属性とプロパティの具体的な使用法と違い

目次Vue.jsにおける属性とプロパティ値および関連する処理として属性とプロパティの概念属性とプロパ...

docker-compose ポートと expose の違いの詳細な説明

docker-compose でコンテナ ポートを公開する方法は、ports と expose の ...

CSSアダプティブレイアウトは、サブ要素項目の全体的な中央揃えと内部項目の左揃えを実現します。

日常業務では、次のようなレイアウトに遭遇することがあります。親要素のフレーム (ブラウザのサイズに応...

MySQL サーバーの接続、切断、および cmd 操作

mysql コマンドを使用して MySQL サーバーに接続します。 MySQL サーバーが起動したら...

vue-cli でレスポンシブ レイアウトを実装する方法

フロントエンド開発を行うと、PCとモバイル端末の適応に必然的に直面することになります。このような問題...