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 ブラックリストの動的禁止の例

推薦する

JavaScript プロトタイプとプロトタイプチェーンの詳細

目次1. プロトタイプ(明示的なプロトタイプ) 2. __proto__ (暗黙のプロトタイプ) 3...

CSS3で実装された水平ヘッダーメニュー

結果:実装コードhtml <nav class="dropdownmenu"...

Ubuntu で起動時に自動的に起動するシェル スクリプトを作成する (推奨)

スクリプトを書く目的は、さまざまなサービスを手動で起動しなくて済むようにすることです(怠惰のためでも...

VUEユニアプリ開発環境についての簡単な説明

目次1. HBuilderXビジュアルインターフェースを通じて2. vue-cliコマンドで実行する...

Vue+express+Socketでチャット機能を実現

この記事では、チャット機能を実現するためのVue+express+Socketの具体的なコードを参考...

シンプルなドラッグ効果を実現するJavaScript

この記事では、簡単なドラッグ効果を実現するためのJavaScriptの具体的なコードを参考までに紹介...

MYSQL サブクエリとネストされたクエリの最適化例の分析

ゲーム史上最高スコアトップ100をチェックSQLコード cdb_playsgame ps から ps...

ウェブサイトのパフォーマンスを向上させるためのウェブサーバーの改善

<br />このシリーズの最初のセクションでは、Web サイトのパフォーマンスを向上させ...

Nginx プロキシを使用してフロントエンドのクロスドメイン問題を解決する方法

序文Nginx (「エンジン エックス」と発音) は、リバース プロキシ、ロード バランサ、HTTP...

Linux コマンドライン操作 Baidu クラウドのファイルのアップロードとダウンロード

目次0. 背景1. インストール2. Baidu Cloudアカウントにログインする3. ファイルを...

Python ベースの MySQL レプリケーション ツールの詳細な説明

目次1. はじめに2回目の練習2.1 インストールと設定2.2 コアクラスの紹介2.3 使い方は? ...

Squid を使用して http および https 用のプロキシ サーバーを構築する方法

nginx を導入した際に、フォワードプロキシの設定も nginx を使っていました。しかし、htt...

secure_file_priv nullの問題を解決する

secure_file_priv = ' ';管理者としてcmdを実行します。 my...

Server-U 14バージョンのインストールと使用方法

Server-Uソフトウェアの紹介Server-U は非常に強力なファイル マネージャーです。FTP...