簡體   English   中英

如何使用主機提供的速率限制來限制我的 JS API 獲取請求?

[英]How to throttle my JS API fetch requests, using the rate-limit supplied by the host?

到目前為止,這是我的代碼:

const allRows = [];

async function fileToLines(file) {
  return new Promise((resolve, reject) => {
    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 => {
      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 => response.json())
  .then(data => {
    if (data.message === 'Release not found.') {
      return { error: `Release with ID ${idFiltered} does not exist` };
    } else {
      const id = data.id;
      const delimiter = document.getElementById("delimiter").value || "|";
      const artists = data.artists ? data.artists.map(artist => artist.name) : [];
      const barcode = data.identifiers.filter(id => id.type === 'Barcode')
      .map(barcode => 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 => label.name) : [];
      const formattedLabels = labels.join(delimiter);
      const catno = data.labels ? data.labels.map(catno => catno.catno) : [];
      const formattedCatNo = catno.join(delimiter);
      const styles = data.styles || [];
      const formattedStyles = styles.join(delimiter);
      const tracklist = data.tracklist ? data.tracklist
      .map(track => track.title) : [];
      const formattedTracklist = tracklist.join(delimiter);
      const year = data.year || 'Unknown';
      const format = data.formats ? data.formats.map(format => format.name) : [];
      const qty = data.formats ? data.formats.map(format => format.qty) : [];
      const descriptions = data.formats ? data.formats
      .map(descriptions => descriptions.descriptions) : [];
      const preformattedDescriptions = descriptions.toString()
      .replace('"','""').replace(/,/g, ', ');
      const formattedDescriptions = '"' + preformattedDescriptions + '"';

      return [idFiltered,
        artists,
        format,
        qty,
        formattedDescriptions,
        formattedLabels,
        formattedCatNo,
        country,
        year,
        formattedGenres,
        formattedStyles,
        formattedBarcode,
        formattedTracklist
      ];
    }
  });
}

function download() {
  const ROW_NAMES = [
    "release_id",
    "artist",
    "format",
    "qty",
    "format descriptions",
    "label",
    "catno",
    "country",
    "year",
    "genres",
    "styles",
    "barcode",
    "tracklist"
  ];
  var csvContent = "data:text/csv;charset=utf-8,"
  + ROW_NAMES + "\n" + allRows.map(e => e.join(",")).join("\n");

  console.log(csvContent);

  var encodedUri = encodeURI(csvContent);
  var link = document.createElement("a");
  link.setAttribute("href", encodedUri);
  link.setAttribute("download", "my_data.csv");
  document.body.appendChild(link); // Required for FF
  link.click();
}

當我在 2.5 年前試圖解決這個問題時(,)有人告訴我最簡單的方法是“保持一系列承諾來跟蹤請求”。 像這樣...

  const timer = ms => new Promise(resolve => setTimeout(resolve, ms));

  let requests = Promise.resolve();

  function getRelease(id) {
   const apiCall = requests.then(() =>
    fetch(`https://api.***.com/releases/${id}`, {
      headers: {
        'User-Agent': '***/0.1',
      }
    })   
   );

   // add to chain / queue 
   requests = apiCall.then(response => 
    +response.headers.get("X-***-Ratelimit-Remaining") <= 1 && timer(60 * 1000)
   );

   return apiCall
     .then(response => response.json())
     .then(parseReleaseData);
  }

建議此代碼的人評論了...

現在一個請求將一個接一個地完成,如果達到速率限制,它會等待一分鍾。

如果出現速率限制錯誤,您可能需要重試。 您還可以添加多個 promise 隊列以實現更高的吞吐量。

似乎當我之前嘗試過時,它在撥打任何電話之前設置了 60 秒的延遲? 我想我想再次嘗試這種方法,但我不確定如何編碼。 就像,我不確定const apiCall = requests.then(() =>是否適合我當前的代碼。我可以看到建議的代碼實際上返回“apiCall”,而我的方法設置為返回所有單獨的數據字段,所以我不知道如何在那里繼續。這似乎是從主機獲取Ratelimit並根據需要設置超時的好方法,但我只是不確定從哪里開始。請幫助?

編輯:我一直在嘗試這樣做,但它仍然不起作用:

const timer = ms => new Promise(resolve => setTimeout(resolve, ms));

const createThrottler = (rateLimit) => {
  let requestTimestamp = 0;
  return (requestHandler) => {
    return async (...params) => {
      const currentTimestamp = Math.floor(Date.now() / 1000);
      if (currentTimestamp < 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);

Edit2:我想知道是否有問題,我將這一行注釋掉了:

const rateLimit = Math.floor((60 / response.headers.get("X-Discogs-Ratelimit-Remaining")) * 1000);

所以我試着取消評論它,但現在我明白了

未捕獲的 ReferenceError:未定義response

Edit3:我得到了一個讓createThrottler() function 工作的建議:-

const rateLimit = 2500;

const timer = ms => new Promise(resolve => setTimeout(resolve, ms));

const createThrottler = (rateLimit) => {
  let requestTimestamp = 0;
  return (requestHandler) => {
    return async (...params) => {
      const currentTimestamp = Number(Date.now());
      if (currentTimestamp < requestTimestamp + rateLimit) {
        const timeOut = rateLimit - (currentTimestamp - requestTimestamp);
        requestTimestamp = Number(Date.now()) + timeOut;
        await timer(timeOut)
      }
      requestTimestamp = Number(Date.now());
      return await requestHandler(...params);
    }
  }
}

不能說我會為自己解決這個問題,但我們做到了。 所以現在我正試圖弄清楚如何以及在哪里編碼

const rateLimit = Math.floor((60 / response.headers.get("X-Discogs-Ratelimit-Remaining")) * 1000);

沒有得到

未捕獲(承諾中)ReferenceError:未定義響應

你看過debounce嗎?

您可以在任何定義的時間段內對 1 個呼叫進行收費限制。 將其視為量化。 另一種方法是在延長的時間范圍內計算呼叫,然后無限期地阻止進一步的呼叫或在定義的持續時間內阻止進一步的呼叫——這取決於您的首選用例。

通常,速率限制更多地與安全性有關,第一個選項(在定義的時間段內提供一次呼叫服務)是恰當的。 如果您正在為 web API 執行此操作,您可能希望拒絕“過早”的請求,並使用適當的 HTTP 狀態代碼向請求者提供某種類型的反饋。

如何實現所有不同的選項在這里討論: https://thoughtsile.github.io/2018/07/07/rate-limit-promises/

試着用 async/await 解決..

試試promise-ratelimit

從他們的文檔中:

var throttle = require('promise-ratelimit')(2000); /* rateInMilliseconds */

var startTime = Date.now();

for (var i = 0; i < 10; i++) {
    throttle().then(function() { console.log(Date.now() - startTime); });
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM