title: How to throttle my JS API fetch requests, using the rate-limit supplied by the host?
body: <p>Here is my code so far:</p>
  <pre><code>const allRows = [];
  
  async function fileToLines(file) {
    return new Promise((resolve, reject) =&gt; {
      reader = new FileReader();
      reader.onload = function(e) {
        parsedLines = e.target.result.split(/\r|\n|\r\n/);
        resolve(parsedLines);
      };
      reader.readAsText(file);
    });
  }
  
  document
  .getElementById('fileInput')
  .addEventListener('change', async function(e) {
    var file = e.target.files[0];
  
    if (file != undefined) {
      fileToLines(file).then( async id =&gt; {
        console.log(id)
        console.log(parsedLines)
        console.log(typeof id);
  
        var idInt = id.map(Number);
        var idFiltered = id.filter(function(v){return v!==''});
  
        console.log(idFiltered)
  
        for(let id of idFiltered) {
          const row = await getRelease(id);
          allRows.push(row);
        }
        download();
      });
    }
  });
  
  function getRelease(idFiltered) {
    return fetch(`https://api.***.com/releases/${idFiltered}`, {
      headers: {
      'User-Agent': '***/0.1',
      },
    })
    .then(response =&gt; response.json())
    .then(data =&gt; {
      if (data.message === 'Release not found.') {
        return { error: `Release with ID ${idFiltered} does not exist` };
      } else {
        const id = data.id;
        const delimiter = document.getElementById(&quot;delimiter&quot;).value || &quot;|&quot;;
        const artists = data.artists ? data.artists.map(artist =&gt; artist.name) : [];
        const barcode = data.identifiers.filter(id =&gt; id.type === 'Barcode')
        .map(barcode =&gt; barcode.value);
        var formattedBarcode = barcode.join(delimiter);
        const country = data.country || 'Unknown';
        const genres = data.genres || [];
        const formattedGenres = genres.join(delimiter);
        const labels = data.labels ? data.labels.map(label =&gt; label.name) : [];
        const formattedLabels = labels.join(delimiter);
        const catno = data.labels ? data.labels.map(catno =&gt; catno.catno) : [];
        const formattedCatNo = catno.join(delimiter);
        const styles = data.styles || [];
        const formattedStyles = styles.join(delimiter);
        const tracklist = data.tracklist ? data.tracklist
        .map(track =&gt; track.title) : [];
        const formattedTracklist = tracklist.join(delimiter);
        const year = data.year || 'Unknown';
        const format = data.formats ? data.formats.map(format =&gt; format.name) : [];
        const qty = data.formats ? data.formats.map(format =&gt; format.qty) : [];
        const descriptions = data.formats ? data.formats
        .map(descriptions =&gt; descriptions.descriptions) : [];
        const preformattedDescriptions = descriptions.toString()
        .replace('&quot;','&quot;&quot;').replace(/,/g, ', ');
        const formattedDescriptions = '&quot;' + preformattedDescriptions + '&quot;';
  
        return [idFiltered,
          artists,
          format,
          qty,
          formattedDescriptions,
          formattedLabels,
          formattedCatNo,
          country,
          year,
          formattedGenres,
          formattedStyles,
          formattedBarcode,
          formattedTracklist
        ];
      }
    });
  }
  
  function download() {
    const ROW_NAMES = [
      &quot;release_id&quot;,
      &quot;artist&quot;,
      &quot;format&quot;,
      &quot;qty&quot;,
      &quot;format descriptions&quot;,
      &quot;label&quot;,
      &quot;catno&quot;,
      &quot;country&quot;,
      &quot;year&quot;,
      &quot;genres&quot;,
      &quot;styles&quot;,
      &quot;barcode&quot;,
      &quot;tracklist&quot;
    ];
    var csvContent = &quot;data:text/csv;charset=utf-8,&quot;
    + ROW_NAMES + &quot;\n&quot; + allRows.map(e =&gt; e.join(&quot;,&quot;)).join(&quot;\n&quot;);
  
    console.log(csvContent);
  
    var encodedUri = encodeURI(csvContent);
    var link = document.createElement(&quot;a&quot;);
    link.setAttribute(&quot;href&quot;, encodedUri);
    link.setAttribute(&quot;download&quot;, &quot;my_data.csv&quot;);
    document.body.appendChild(link); // Required for FF
    link.click();
  }
  </code></pre>
  <p>When I was previously trying to crack this problem 2.5 years ago (!) someone told me the easiest way &quot;is to maintain a chain of promises to keep track of the requests&quot;, like this...</p>
  <pre><code>  const timer = ms =&gt; new Promise(resolve =&gt; setTimeout(resolve, ms));
  
    let requests = Promise.resolve();
  
    function getRelease(id) {
     const apiCall = requests.then(() =&gt;
      fetch(`https://api.***.com/releases/${id}`, {
        headers: {
          'User-Agent': '***/0.1',
        }
      })   
     );
  
     // add to chain / queue 
     requests = apiCall.then(response =&gt; 
      +response.headers.get(&quot;X-***-Ratelimit-Remaining&quot;) &lt;= 1 &amp;&amp; timer(60 * 1000)
     );
  
     return apiCall
       .then(response =&gt; response.json())
       .then(parseReleaseData);
    }
  </code></pre>
  <p>The person who suggested this code commented...</p>
  <blockquote>
  <p>Now one request will be done after another, and if the rate limit gets reached it waits a minute.</p>
  </blockquote>
  <blockquote>
  <p>You might want to retry in case of a rate limiting error. You could also add multiple promise queues to allow for higher throughput.</p>
  </blockquote>
  <p>It seems that when I tried that previously, it set a 60s delay before making any calls? I think I would like to try this method again, but I'm not sure how to code it. Like, I'm not sure how <code>const apiCall = requests.then(() =&gt;</code> would fit in with my current code. I can see that the suggested code actually returns 'apiCall', whereas my method is set up to return all the individual data fields, so I'm not sure how to proceed there. It seems like a good method to get the <code>Ratelimit</code> from the host and set a timeout as needed, but I'm just not sure where to start really. Any help please?</p>
  <p>Edit: I've been trying to do it like this, but it still doesn't work:</p>
  <pre><code>const timer = ms =&gt; new Promise(resolve =&gt; setTimeout(resolve, ms));
  
  const createThrottler = (rateLimit) =&gt; {
    let requestTimestamp = 0;
    return (requestHandler) =&gt; {
      return async (...params) =&gt; {
        const currentTimestamp = Math.floor(Date.now() / 1000);
        if (currentTimestamp &lt; requestTimestamp + rateLimit) {
          await timer(rateLimit - (currentTimestamp - requestTimestamp))
        }
        requestTimestamp = Math.floor(Date.now() / 1000);
        return await requestHandler(...params);
      }
    }
  }
  
  const throttle = createThrottler(2500);
  
  const throttleFetch = throttle(fetch);
  </code></pre>
  <p>Edit2: I was wondering if there was a problem that I had this line commented out:</p>
  <p><code>const rateLimit = Math.floor((60 / response.headers.get(&quot;X-Discogs-Ratelimit-Remaining&quot;)) * 1000);</code></p>
  <p>So I tried un-commenting it, but now I get</p>
  <blockquote>
  <p>Uncaught ReferenceError: <code>response</code> is not defined</p>
  </blockquote>
  <p>Edit3: I got a suggestion that get the <code>createThrottler()</code> function working:-</p>
  <pre><code>const rateLimit = 2500;
  
  const timer = ms =&gt; new Promise(resolve =&gt; setTimeout(resolve, ms));
  
  const createThrottler = (rateLimit) =&gt; {
    let requestTimestamp = 0;
    return (requestHandler) =&gt; {
      return async (...params) =&gt; {
        const currentTimestamp = Number(Date.now());
        if (currentTimestamp &lt; requestTimestamp + rateLimit) {
          const timeOut = rateLimit - (currentTimestamp - requestTimestamp);
          requestTimestamp = Number(Date.now()) + timeOut;
          await timer(timeOut)
        }
        requestTimestamp = Number(Date.now());
        return await requestHandler(...params);
      }
    }
  }
  </code></pre>
  <p>Can't say I would ever have worked that out for myself, but there we are. So now I am trying to work out how and where to code</p>
  <pre><code>const rateLimit = Math.floor((60 / response.headers.get(&quot;X-Discogs-Ratelimit-Remaining&quot;)) * 1000);
  </code></pre>
  <p>without getting</p>
  <blockquote>
  <p>Uncaught (in promise) ReferenceError: response is not defined</p>
  </blockquote>