どのようにJavaScriptフェッチの進行状況を監視する-要求とオンデマンドでキャンセルします.


TL : DR ->コードを私にしてください.https://github.com/tq-bit/fetch-progress
In an earlier post, 私はすでにフェッチを使用してAPIと対話する方法の概要を与えている.この記事では、より詳細な2つのユースケースに詳しく説明します.
  • HTTPリクエストを作成中にダウンロードの進行状況を監視します.
  • ユーザーの入力で要求を優雅に取り消す.
  • 次のようにしたい場合は、このgithubブランチを使用して起動できます.JavaScriptは、いくつかのスタイルとHTMLが含まれます:https://github.com/tq-bit/fetch-progress/tree/get-started .

    これは私たちが出発するUIです.進捗インジケータは、フェッチを可視化されます
    だからあなたのお気に入りのコードエディタをスピンしてダイビングをしましょう.

    基本的なフェッチ要求を作成する


    先進のものから始める前に、単純な機能を構築しましょう.タスクは、大学を検索することができますユーティリティコードの一部を開発することです.幸運にもHipo ちょうど上に構築するツールがあります.
  • 使っているthis repository 'sは出発地としてAPIをホストしました.
  • ルートURLはhttp://universities.hipolabs.com/ .
  • 私はクエリを使用して米国のすべての大学に私の検索を制限したいと思います.
  • 技術面では、私のフェッチロジックをラッパー関数内に保ちたい.
  • 次のコードを追加することから始めましょうclient.js ファイル
    export default function http(rootUrl) {
      let loading = false;
    
      let chunks = [];
      let results = null;
      let error = null;
    
    
      // let controller = null; // We will get to this variable in a second
    
      const json = async (path, options,) => {
        loading = true
    
        try {
          const response = await fetch(rootUrl + path, { ...options });
    
          if (response.status >= 200 && response.status < 300) {
            results = await response.json();
            return results
          } else {
            throw new Error(response.statusText)
          }
        } catch (err) {
          error = err
          results = null
          return error
        } finally {
          loading = false
        }
      }
    
      return { json }
    }
    
    次に、この関数をmain.js ファイルを初期化します.
    // Import the fetch client and initalize it
    import http from './client.js';
    const { json } = http('http://universities.hipolabs.com/');
    
    // Grab the DOM elements
    const progressbutton = document.getElementById('fetch-button');
    
    // Bind the fetch function to the button's click event
    progressbutton.addEventListener('click', async () => {
      const universities = await json('search?country=United+States');
      console.log(universities);
    });
    
    フェッチボタンをクリックすると、要求された大学をコンソールに出力します.

    再構築します。JSON ()メソッド


    進捗状況を監視するには、標準の良い部分を再構築する必要があります.json() メソッド.また、我々はまた、チャンクによってチャンク応答体を組み立てるの世話をしなければならないことを暗示する.

    I've written an article about handling Node.js streams earlier. The approach shown here is quite similar.


    次のように追加しましょうclient.js ファイルの下にjson 機能
    export default function http(rootUrl) { 
    
      // ... previous functions
      const _readBody = async (response) => {
        const reader = response.body.getReader();
    
        // Declare received as 0 initially
        let received = 0;
    
        // Loop through the response stream and extract data chunks
        while (loading) {
          const { done, value } = await reader.read();
          if (done) {
            // Finish loading 
            loading = false;
          } else {
            // Push values to the chunk array
            chunks.push(value);
          }
        }
    
        // Concat the chinks into a single array
        let body = new Uint8Array(received);
        let position = 0;
    
        // Order the chunks by their respective position
        for (let chunk of chunks) {
          body.set(chunk, position);
          position += chunk.length;
        }
    
        // Decode the response and return it
        return new TextDecoder('utf-8').decode(body);
      }
      return { json }
    }
    
    次は入れ替えましょうresponse.json() 次のようになります.
      // results = response.json();
      // return results;
      results = await _readBody(response)
      return JSON.parse(results)
    
    ブラウザでの応答は、以前にデコードされたJSONオブジェクトと同じです.レスポンスの本体自体がreadable stream , 新しいデータが読み込まれているか、ストリームが閉じられているかを監視することができます.

    最大と現在のデータ長を取得する


    進捗監視のための2つのコア番号がここにあります.
  • The content-length レスポンスからのヘッダ、変数length .
  • 計算されたlength 受信データの塊、変数received .
  • Note that this function does not work if the content-length header is not configured on the serverside.


    既に変数を持っているのでreceived ご利用いただけますcontent-length 我々に_readBody 機能
      const _readBody = async (response) => {
        const reader = response.body.getReader();
    
        // This header must be configured serverside
        const length = +response.headers.get('content-length'); 
    
        // Declare received as 0 initially
        let received = 0; 
      // ...
      if (done) {
          // Finish loading
          loading = false;
        } else {
          // Push values to the chunk array
          chunks.push(value);
    
          // Add on to the received length
          received += value.length; 
        }
      }
    
    それで、我々はすべての関連指標値をご利用いただけます.不足しているのは、呼び出し元の関数にそれらを放出する方法です.これは簡単にJavaScriptフレームワークの反応機能を使用して、反応フックやVueの組成APIのように行うことができます.この場合、しかし、我々は組み込みブラウザ機能CustomEvent .

    イベントで取得可能なフェッチプログレスを作る


    監視機能をラップするには、2つのカスタムイベントを作成します.
  • データチャンクを読み込むたびに、イベントfetch-progress .
  • 取得要求が終了したとき、イベントfetch-finished .
  • 両方のイベントはウィンドウオブジェクトにバインドされます.このように、彼らはhttp - 関数のスコープ.
    インサイド_readBody() , 調整.次のようにループします.
      const _readBody = async (response) => {
        // ...
    
        // Loop through the response stream and extract data chunks
        while (loading) {
          const { done, value } = await reader.read();
          const payload = { detail: { received, length, loading } }
          const onProgress = new CustomEvent('fetch-progress', payload);
          const onFinished = new CustomEvent('fetch-finished', payload)
    
          if (done) {
            // Finish loading
            loading = false;
    
            // Fired when reading the response body finishes
            window.dispatchEvent(onFinished)
          } else {
            // Push values to the chunk array
            chunks.push(value);
            received += value.length;
    
            // Fired on each .read() - progress tick
            window.dispatchEvent(onProgress); 
          }
        }
        // ... 
      }
    

    ディスプレイの進行状況


    取得する最終的な手順は、カスタムイベントをキャッチし、それに応じて進捗バーの値を変更します.ジャンプしましょうmain.js ファイルを次のように調整します.
  • 適切なDOM要素を取得する
  • イベントリスナーを追加するfetch-progress
  • イベントリスナーを追加するfetch-finished
  • 次に、e.detail プロパティとプログレスバー値を調整します.
  • // Import the fetch client and initalize it
    import http from './client.js';
    
    // Grab the DOM elements
    const progressbar = document.getElementById('progress-bar');
    const progressbutton = document.getElementById('fetch-button');
    const progresslabel = document.getElementById('progress-label');
    const { json } = http('http://universities.hipolabs.com/');
    
    const setProgressbarValue = (payload) => {
      const { received, length, loading } = payload;
      const value = ((received / length) * 100).toFixed(2);
      progresslabel.textContent = `Download progress: ${value}%`;
      progressbar.value = value;
    };
    
    // Bind the fetch function to the button's click event
    progressbutton.addEventListener('click', async () => {
      const universities = await json('search?country=United+States');
      console.log(universities);
    });
    
    window.addEventListener('fetch-progress', (e) => {
      setProgressbarValue(e.detail);
    });
    
    window.addEventListener('fetch-finished', (e) => {
      setProgressbarValue(e.detail);
    });
    
    そして、我々はそれを持っている-あなたは今あなたのフェッチ要求の進行状況を監視することができます.
    それでも、いくつかの調整があります.
  • スコープ変数のリセット
  • リクエストをキャンセルする
  • あなたが読書でこれまでに来たならば、2、3のより多くの線のために私と一緒にいてください.

    スコープ変数のリセット


    これは音として簡単で、私たちに素敵な、再利用可能な機能を提供します.
    次の右側に_readBody() - あなたの関数client.js ファイル
    const _resetLocals = () => {
      loading = false;
    
      chunks = [];
      results = null;
      error = null;
    
      controller = new AbortController();
    }
    

    Remeber that you must call resetLocals() in the json() function first.


    export default function http(rootUrl) {
      let loading = false;
    
      let chunks = [];
      let results = null;
      let error = null;
    
      let controller = null; // Make sure to uncomment this variable
      const json = async (path, options,) => {
        _resetLocals();
        loading = true
      // ... rest of the json function
      }
    // ... rest of the http function
    
    上記の機能で、私たちはまた、新しいオブジェクトAbortController . 名前が示すように、アクティブリクエストをカットするために使用できます.

    進行中の要求を取り消す


    作成されたAbortControllerを使用して、我々はすぐに信号を作成することができます.それはコントローラ自体と発信するHTTP要求の間の通信インターフェースとして機能します.内蔵のキルスイッチのように想像してください.
    それを設定するにはclient.js ファイル:
  • シグナルを作成し、フェッチリクエストオプションに渡します.
  • コントローラの中止関数を呼び出す新しい関数を作成します.
  • const json = async (path, options,) => {
      _resetLocals();
      let signal = controller.signal; 
      loading = true
    
      try {
        const response = await fetch(rootUrl + path, { signal, ...options });
      // ... rest of the trycatch function
      }
    // ... rest of the json function
    }
    
    // Cancel an ongoing fetch request
    const cancel = () => {
      _resetLocals();
      controller.abort();
    };
    
    // Make sure to export cancel
    return { json, cancel }
    
    最後にジャンプしましょうmain.js イベントを2番目のボタンにバインドする
    // ... other variable declarations
    const abortbutton = document.getElementById('abort-button');
    const { json, cancel } = http('http://universities.hipolabs.com/');
    
    // ... other functions and event listeners
    abortbutton.addEventListener('click', () => {
      cancel()
      alert('Request has been cancelled')
    })
    
    あなたがすぐにFETCHとCancel要求を直後にヒットするならば、あなたがそれが200のHTTPステータスを返すとしても、要求がデータを全く返さないという警告を見るでしょう.

    UPDATE : VUE 3フェッチ機能


    私はVue 3の組成APIを使用して、この機能を再現している.Vueアプリでの監視要求をキャンセルし、要求をキャンセルする場合は、このGISTを見てください.
    https://gist.github.com/tq-bit/79d6ab61727ebf29ed0ff9ddc4deedca

    次は?


    残念ながら、私がこの記事のために研究した時間によって、私はアップロード進展をモニターする一般的な方法を見つけることができませんでした.公式WhatWG Githubリポジトリにはopen issue という特徴でFetchObserver . しかし、我々はそれが実装されるために我慢しなければならないようです.おそらく、この記事に記載されている機能も簡単になります.未来は語るだろう.
    https://github.com/whatwg/fetch/issues/607

    This post was originally published at https://blog.q-bit.me/monitoring-and-canceling-a-javascript-fetch-request/
    Thank you for reading. If you enjoyed this article, let's stay in touch on Twitter 🐤