Как реализовать задачу с одновременными запросами на сервер?
Всем привет. Есть функция, она принимает на вход массив адресов на которые будет происходить запрос и количество максимальных запросов (количество максимальных запросов в очереди). Если количество запросов больше чем второй параметр (очередь заполнена), то мы должны дождаться когда можно будет уменьшить очередь и продолжить делать следующие запросы по описанному принципу заполнения очереди.
Ниже я написал начало решения, вопрос именно в том как в коде написать очистку очереди и продолжение выполнение запросов? const sendRequests = (requests, maxRequestsCount) => { return new Promise(resolve => { const responses = []; let requestsCount = 0; for (let i = 0; i < requests.length; i++) { const request = requests[i]; requestsCount += 1; if (requestsCount !== maxRequestsCount) { makeRequest(request); } } function makeRequest(url) { return fetch(url) .then(result => result.json()) .catch(error => error) .then(result => { responses.push(result); resolve(responses); }); } }); } console.log( sendRequests([ 'https://jsonplaceholder.typicode.com/todos/2', 'https://jsonplaceholder.typicode.com/todos/4', 'https://jsonplaceholder.typicode.com/todos/10', 'https://jsonplaceholder.typicode.com/todos/18' ], 3).then(response => { console.log(response); }) ); |
Цитата:
|
Очередь считается заполненной, когда ушло максимальное количество запросов (второй параметр). Очистить очередь на один запрос можно тогда, когда этот запрос выполнился.
|
Может такой вариант
const sendRequests = (requests, maxRequestsCount) => { return new Promise(resolve => { const responses = Array.from({length: requests.length}, _ => null); let curUrlInd = 0; let requestDone = 0; for (let i = 0; i < maxRequestsCount; i++) { makeRequest(requests[i], i); } curUrlInd = maxRequestsCount; function makeRequest(url, n) { return fetch(url) .then(result => result.json()) .catch(error => error) .then(result => { responses[n] = result; }) .finally (() => { if (curUrlInd < requests.length) { makeRequest(requests[curUrlInd], curUrlInd); curUrlInd++; } requestDone ++; if (requestDone === requests.length) { resolve(responses); } }); } }); } console.log( sendRequests([ 'https://jsonplaceholder.typicode.com/todos/2', 'https://jsonplaceholder.typicode.com/todos/4', 'https://jsonplaceholder.typicode.com/todos/10', 'https://jsonplaceholder.typicode.com/todos/18' ], 3).then(response => { console.log(response); }) ); |
Цитата:
|
Цитата:
const responses = new Array(requests.length); P.S. И да, работать как требует dc65k оно не будет: осле первых трёх количество запросов будет нарастать без контроля, а должно быть всегда три.) |
Цитата:
В данном случае, конечно без разницы, но я так не делаю new Array(n) создает "дырявый" массив, который очень неэффективен при всяких оптимизациях. |
Цитата:
<html> <script> const fakefetch = (url) =>{ return new Promise (res => { dt = (Math.random()*2+1)*1000 | 0 setTimeout(() => res({url:url, dt: dt}), dt) }) } const sendRequests = (requests, maxRequestsCount) => { return new Promise(resolve => { const responses = Array.from({length: requests.length}, _ => null); let curUrlInd = 0; let requestDone = 0; for (let i = 0; i < maxRequestsCount; i++) { const request = requests[i]; makeRequest(requests[i], i); } curUrlInd = maxRequestsCount async function makeRequest(url, n) { console.log('Request', n) return fakefetch(url) // .then(result => result.json()) // .catch(error => error) .then(result => { responses[n] = result; }) .finally (() => { requestDone ++; console.log('Done', n, requestDone) if (curUrlInd < requests.length) { makeRequest(requests[curUrlInd], curUrlInd); curUrlInd++; } if (requestDone === requests.length) { console.log ('Done All') resolve(responses); } }); } }); } sendRequests([ 'https://jsonplaceholder.typicode.com/todos/2', 'https://jsonplaceholder.typicode.com/todos/4', 'https://jsonplaceholder.typicode.com/todos/10', 'https://jsonplaceholder.typicode.com/todos/18', 'https://jsonplaceholder.typicode.com/todos/20', 'https://jsonplaceholder.typicode.com/todos/40', 'https://jsonplaceholder.typicode.com/todos/100', 'https://jsonplaceholder.typicode.com/todos/180' ], 3).then(response => { console.log(response); }) </script> </html> Нарастать не будет - новый запрос посылается, когда какой то из ранее посланных закончится. |
voraa, да согласен, тупанул.)
По поводу new Array - я его использую только именно в таком случае и ни в каком ином. Считаю что использовать подходящие инструменты в подходящих местах - стоит, а откидывать те или иные вещи скопом только потому, что в некоторых ситуациях(прекрасно известных) они могут привести к неприятным последствиями, ограничивая себя узкими рамками "безопасного" кода - не стоит.) Обычно всё это сводится к "а вот к команде присоединится говнокодер Вася, и всё сломает", но я не хочу и не буду ради какого-то Васи писать менее органичный и красивый код.) |
dc65k,
Ответь пожалуйста, для чего нужно подобное извращение? |
Цитата:
Array(n).fill(0) |
Vlasenko Fedor, ну например возможно jsonplaceholder держит только три соединения, а больше - дропает.)
Частая ситуация, так-то. |
Aetae,
сервер для одного клиента ? для чего тогда балансировщики и что делать с ошибками в таком случае? |
Vlasenko Fedor, ты странный. Обычная антиспам\ антидос\ антибрутфорс система. На эндпоинт один клиент может послать только N запросов за раз. Это буквально везде.
Балансировщики - балансируют разные запросы разных клиентов меж разными серверами, они тут не причём. С ошибками делать то же что и всегда - как-либо обрабатывать. |
Цитата:
Ибо в эти системах есть понятие время в количество запросов, а не колличество только Потому я изадал вопрос ТС, чтоб он пояcнил зачем это Пример http://nginx.org/ru/docs/http/ngx_ht...eq_module.html |
Vlasenko Fedor, што?
Вот прямо по твоей ссылке: ставим limit_req zone=one burst=3 nodelay; получаем именно такой результат. "N запросов за раз" - очевидно очевидно значит "N запросов за минимальный промежуток времени установленный на сервере в подавляющем большинстве случаев равный одной секунде". Ты думал я о планковских величинах тут говорю, или что? |
Aetae,
неужели непонятно, что лимит запросов идет по времени давй на пальцах, как ты поймешь что превышен лимит ? будешь считать запросы и говорить, что 4 уже с 503 ответом? нет ты будешь смотреть в каком интервале времени прошли запросы вот ответ на твой пример |
Vlasenko Fedor,
Цитата:
Или ты намекаешь, что может быть такой случай, когда ответы на все три запроса придут раньше чем через секунду, из-за чего четвёртый запрос будет отправлен до истечения секундного интервала из-за чего получится 4 запроса в секунду и он будет дропнут? Конкретно в данном случае сам механизм работы burst не допустит этого, но и в целом в реальном мире это крайне маловероятно. В идеальном случае действительно желательно точно знать, что за механизмы и тайминги используются там на сервере, и высчитывать доп задержку для надёжности, если ты имел ввиду именно это. |
Цитата:
https://v8.dev/blog/elements-kinds Конкретнее https://v8.dev/blog/elements-kinds#avoid-creating-holes Если массив стал (изначально создан) дырявым, то он дырявым и останется, что с ним не делай. V8 будет считать его дырявым, и не применять к нему оптимизаций, как к заполненному. Я когда то делал тесты. Выигрыш может и не самый впечатляющий - 5-7%. Но все таки выигрыш. |
voraa, ну это прям наносекунды. Тогда и методы массива вообще юзать не надо - просто циклы. Конечно же никакого spread и destructuring. А ещё можно выиграть на создании объектов вот так, да: https://v8.dev/blog/cost-of-javascript-2019#json .)
|
Конечно можно считать, что в гуглах сидят программеры, которые от избытка денег и времени делают никому не нужные оптимизации.
Цитата:
|
voraa, неплохо. Но всё равно в 100500 раз больше времени занимает, чем потенциальные расходы от дырок.)
По твоей же ссылке прямо пишут "the performance difference between accessing holey or packed arrays is usually too small to matter or even be measurable". |
Цитата:
Запилил исправленную версию, которая позволяет отправлять несколько порций. Причем, вторая порция может начать исполняться ещё до того, как завершится первая. function createSendRequests(func, limit) { let reqCount = 0; const queue = []; return function (requests) { console.log('sendRequests for ', requests.join(',')); if (!requests || !requests.length) { return Promise.resolve([]); } return new Promise((resolve) => { const responses = Array.from({length: requests.length}); let curUrlInd = 0; let requestDone = 0; while (curUrlInd < requests.length && reqCount < limit) { makeRequest(curUrlInd); } let inQueue = curUrlInd < requests.length; if (inQueue) { queue.push(nextRequest); } function nextRequest() { makeRequest(curUrlInd); if (inQueue && curUrlInd === requests.length) { inQueue = false; queue.shift(); } } function makeRequest(n) { reqCount++; curUrlInd++; const req = requests[n]; console.log('request for ', req); func(req).then(data => { console.log('response for ', req); responses[n] = { data }; }, (err) => { responses[n] = { error: err || 'error' }; }).finally (() => { reqCount--; if (queue.length) { queue[0](); } requestDone++; if (requestDone === requests.length) { resolve(responses); } }); } }); }; } // ----- использование ------------------------------------- function myFetch(req) { return new Promise((res) => { setTimeout(res, 1000, 'response for ' + req); }); } var sendRequests = createSendRequests(myFetch, 5); sendRequests([ 'reqA', 'reqB', ]).then((data) => { console.log('responses 1', data); }); sendRequests([ 'req1', 'req2', 'req3', 'req4', 'req5', 'req6', 'req7', ]).then((data) => { console.log('responses 2', data); }); sendRequests([ 'req8', 'req9', 'req10', 'req11', 'req12', ]).then((data) => { console.log('responses 3', data); }); |
Часовой пояс GMT +3, время: 06:05. |