Как сделать длинный список http-звонков в сериале? - PullRequest
1 голос
/ 16 апреля 2020

Я пытаюсь сделать только один http-вызов за раз, но когда я регистрирую ответ от getUrl, они накапливаются, и я начинаю получать 409 с (слишком много запросов)

function getUrl(url, i, cb) {
  const fetchUrl = `https://api.scraperapi.com?api_key=xxx&url=${url.url}`;

  fetch(fetchUrl).then(async res => {
    console.log(fetchUrl, 'fetched!');
    if (!res.ok) {
      const err = await res.text();
      throw err.message || res.statusText;
    }

    url.data = await res.text();
    cb(url);
  });
 }


let requests = urls.map((url, i) => {
  return new Promise(resolve => {
    getUrl(url, i, resolve);
  });
});

const all = await requests.reduce((promiseChain, currentTask) => {
  return promiseChain.then(chainResults =>
    currentTask.then(currentResult => [...chainResults, currentResult]),
  );
}, Promise.resolve([]));

В основном Я не хочу, чтобы следующий http начинался, пока не закончился предыдущий. В противном случае я забью их сервер.

БОНУСНЫЕ ОЧКИ: Сделайте эту работу с 5 одновременно за один раз.

Ответы [ 3 ]

1 голос
/ 16 апреля 2020

Поскольку вы используете await, было бы намного проще использовать это везде, вместо того, чтобы путать .then s с reduce. Также было бы неплохо избегать явной * anti-pattern конструкции Promise. Это должно делать то, что вы хотите:

const results = [];
for (const url of urls) {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(response); // or whatever logic you need with errors
  }
  results.push(await response.text());
}

Тогда ваша переменная results будет содержать массив текстов ответов (или будет выдана ошибка, и код не достигнет дна).

Синтаксис для функции async - это ключевое слово async перед списком аргументов, как вы делаете это в исходном коде:

const fn = async () => {
  const results = [];
  for (const url of urls) {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(response); // or whatever logic you need with errors
    }
    results.push(await response.text());
  }
  // do something with results
};

Чтобы иметь ограниченное количество запросы за один раз создают систему очередей - когда запрос завершается, рекурсивно вызывайте функцию, которая делает другой запрос, что-то вроде:

const results = [];
const queueNext = async () => {
  if (!urls.length) return;
  const url = urls.shift();
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(response); // or whatever logic you need with errors
  }
  results.push(await response.text());
  await queueNext();
}
await Promise.all(Array.from({ length: 5 }, queueNext));
// do something with results
0 голосов
/ 16 апреля 2020

async/await идеально подходит для этого.

Предполагается, что у вас есть массив URL-адресов в виде строк:

let urls = ["https://example.org/", "https://google.com/", "https://stackoverflow.com/"];

Вам просто нужно сделать:

for (let u of urls) {
  await fetch(u).then(res => {
    // Handle response
  }).catch(e => {
    // Handle error
  });
}

L oop не будет повторяться до тех пор, пока не разрешится текущий fetch(), что приведет к сериализации.


Причина, по которой array.map не работает, заключается в следующем:

async function doFetch(url) {
  return await fetch(url).then(res => {
    // Handle response
  }).catch(e => {
    // Handle error
  });
}
let mapped = urls.map(doFetch);

эквивалентно:

let mapped;
for (u of urls) {
  mapped.push(doFetch(u));
}

Это немедленно заполнит mapped группой Promise s, что не то, что вам нужно. Вот что вам нужно:

let mapped;
for (u of urls) {
  mapped.push(await doFetch(u));
}

Но это не то, что array.map() делает. Поэтому использование явного for l oop необходимо.

0 голосов
/ 16 апреля 2020

Вы не можете использовать методы Array для последовательного запуска асинхронных c операций, поскольку все методы массива являются синхронными.

Самый простой способ достижения последовательных асинхронных c задач - через al oop. В противном случае вам нужно написать пользовательскую функцию для имитации al oop и запустить .then после завершения асинхронной задачи c, что довольно хлопотно и ненужно.

Кроме того, fetch уже возвращая обещание, поэтому вам не нужно создавать обещание самостоятельно, чтобы содержать это обещание, возвращаемое fetch.

. Приведенный ниже код является рабочим примером с небольшими изменениями в исходном коде (см. комментарии). .

// Fake urls for example purpose
const urls = [{ url: 'abc' }, { url: 'def', }, { url: 'ghi' }];

// To imitate actual fetching
const fetch = (url) => new Promise(resolve => {
  setTimeout(() => {
    resolve({
      ok: true,
      text: () => new Promise(res => setTimeout(() => res(url), 500))
    });
  }, 1000);
});

function getUrl(url, i, cb) {
  const fetchUrl = `https://api.scraperapi.com?api_key=xxx&url=${url.url}`;
  return fetch(fetchUrl).then(async res => { // <-- changes here
    console.log(fetchUrl, 'fetched!');
    if (!res.ok) {
      const err = await res.text();
      throw err.message || res.statusText;
    }

    url.data = await res.text();
    return url; // <--- changes here
  });
}

async function getAllUrls(urls){
  const result = [];
  for (const url of urls){
    const response = await getUrl(url);
    result.push(response);
  }
  return result;
}

getAllUrls(urls)
  .then(console.log);
...