Запрос ограниченного количества конечных точек одновременно

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

const requestQueue = (endpoints, callback, limit = 3) => {
    while (endpoints.length > 0) {
        if (limit > 0) {
            const slice = endpoints.splice(0, limit)
            for (const endpoint of slice) {
                limit--
                fetchMock(endpoint)
                    .then(data => callback(data))
                    .catch(err => callback(err))
                    .finally(() => limit++)
            }
        }
    }
}

function fetchMock(endpoint) {
    return Promise.resolve(endpoint)
}

requestQueue([1, 2, 3, 4, 5], data => console.info(data))

Он входит в бесконечный цикл и падает. Что я упускаю или неправильно понимаю? Помоги пожалуйста.

🤔 А знаете ли вы, что...
С JavaScript можно создавать интерактивные формы и проверять введенные пользователем данные.


5
102
2

Ответы:

Используйте Promise.all для одновременного запуска элементов среза в пакете:

const requestQueue = async (endpoints, callback, limit = 3, offset = 0) => {

    const slice = endpoints.slice(offset, offset + limit);

    if (!slice.length){
        return;        
    }

    // map slice into fetchMock calls and await the batch
    await Promise.all(slice.map(endpoint => fetchMock(endpoint).then(callback).catch(callback)));

    // delay between slices, remove if not needed
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // run the next slice
    return requestQueue(endpoints, callback, limit, offset += limit);
}

function fetchMock(endpoint) {
    return new Promise(resolve => setTimeout(() => resolve(endpoint), Math.random()*1000));
}

requestQueue([1, 2, 3, 4, 5], data => console.info(data))

Решено

Причиной бесконечного цикла является уменьшение переменной limit до нуля внутри цикла for of; в результате условие while цикла endpoints.length > 0 никогда не оценивается как false.

endpoints.length всегда больше нуля, потому что вы не позволяете циклу войти в блок if после первой итерации. После первой итерации limit равно нулю, поэтому limit > 0 не равно true после первой итерации. В результате цикл застревает в бесконечном цикле.

Теперь вы можете сказать: «Но я увеличиваю limit внутри обратного вызова finally, поэтому limit не всегда равно нулю».

Для увеличения limit функция обратного вызова finally должна быть выполнена, НО этого никогда не происходит, потому что асинхронный код (обратные вызовы then, catch и finally вызываются асинхронно) выполняется после завершения выполнения синхронного кода, НО в вашем случае выполнение синхронного кода никогда не заканчивается, потому что он застрял в цикле while. Вы не дали finally обратному вызову шанса быть казненным.

На самом деле ни один из обратных вызовов промисов не вызывается в вашем коде, потому что синхронное выполнение кода должно завершиться для их вызова, и вы надеетесь завершить синхронное выполнение кода после вызова асинхронных обратных вызовов. Ваш код застрял в тупике.


Поскольку вы хотите инициировать запрос к другой конечной точке, как только любой из первоначально запущенных запросов будет завершен, следующий код показывает это в действии:

let offset = 0;

function makeRequest(endpoints, callback) {
  console.info('fetching endpoint ' + endpoints[offset]);
  fetchMock(endpoints[offset++])
    .then(callback)
    .catch(callback)
    .finally(() => {
      if (offset < endpoints.length) {
        requestQueue(endpoints, callback);
      }
    });
}

const requestQueue = (endpoints, callback, limit = 3) => {
  if (offset >= endpoints.length) {
    console.info("returning");
    return;
  }

  if (offset === 0) {
    for (let i = 0; i < limit; i++) {
      makeRequest(endpoints, callback);
    }
  } else {
    makeRequest(endpoints, callback);
  }
};

function fetchMock(endpoint) {
  // random delay between 1 to 4 seconds
  const randomDelay = (Math.random() * (5 - 1) + 1) * 1000;
  return new Promise(res => setTimeout(res, randomDelay, endpoint));
}

requestQueue([1, 2, 3, 4, 5], data => console.info(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }