#javascript #reactjs #promise #axios
#javascript #reactjs #обещание #axios
Вопрос:
В моем приложении React у меня есть компонент, который отправляет запрос в онлайн-службу, способную обрабатывать максимум 50 запросов. Теперь я получил новый запрос на выполнение 7000 компьютеров MAC.
function App() {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
await axios.all([
axios.get("/ipdn/<MAC ADDRESS>", { timeout: 10000 }),
axios.get("/ipdn/<MAC ADDRESS>", { timeout: 10000 })
// Adding all the mac address .......
]).then((responseArr) => {
setData(responseArr)
});
};
fetchData();
}, []);
Я хотел бы расширить функцию fetchData, чтобы в основном она отправляла только 50 IP-адресов и ждала завершения итерации.
Когда итерация будет завершена, будут выполнены следующие 50.
Спасибо
Комментарии:
1. Используйте es6-promise-pool . Это помогает вам управлять параллелизмом с параллельными запросами
2. Не могли бы вы дополнить свой код, чтобы было ясно, где хранятся mac-адреса и как вы передаете их в запрос?
3. Просто не пишите клиентский компонент для выполнения 7000 HTTP-запросов.
Ответ №1:
Без библиотеки вы могли бы использовать эту функцию:
function poolPromises(iterPromises, poolSize) {
return new Promise((resolve, reject) => {
let promises = [];
function nextPromise() {
let { value, done } = iterPromises.next();
if (done) {
resolve(Promise.all(promises));
} else {
promises.push(value); // value is a promise
value.then(nextPromise, reject);
}
return !done;
}
while (promises.length < poolSize amp;amp; nextPromise()) { }
});
}
Эта функция будет принимать обещания от итератора вплоть до размера пула. Всякий раз, когда обещание разрешается, оно получает следующее обещание от итератора, так что пул снова заполняется. Таким образом, пул не обязательно полностью опустошать перед созданием следующего блока обещаний. Как только место освободится, оно будет использовано снова.
Важно, чтобы итератор создавал следующее обещание только тогда, когда оно извлекается из него с помощью next()
метода.
В вашем случае использования вы можете вызвать это следующим образом:
const fetchData = async () => {
function * iterRequests() {
for (let macAddress of macAddresses) {
yield axios.get("/ipdn/" macAddress, { timeout: 10000 });
}
}
return poolPromises(iterRequests(), 50).then(setData);
}
Примечание: fetchData
не обязательно объявлять async
, поскольку там ничего await
нет.
Комментарии:
1. при
while (promises.length < poolSize amp;amp; nextPromise()) { }
и, например,poolSize
максимум 50 обещаниях за раз, но более 50 запросов через*iterRequests
, как вообще удается обрабатывать все обещания? Кто-нибудь когда-нибудь достигаетdone
черезlet { value, done } = iterPromises.next();
?2. Первая партия вызовов инициируется из этого
while
цикла, который обычно заканчивается приpoolSize
достижении, но затем, когда одно из этих 50 обещаний выполняется, та же функцияnextPromise
вызывается снова (черезthen
), и это продолжается до тех пор, покаiterPromises
не будет исчерпан. Итак, в конце все обещания создаются и собираются вpromises
массив, который в вашем случае достиг бы длины 7000.3. Я не читал
value.
, поэтому я полностью пропустил его важность в этой строкеvalue.then(nextPromise, reject);
… спасибо, что терпеливо повторяете теперь очевидное для меня.4. Мне нравится этот подход все больше и больше… за его чистоту и, следовательно, красоту того, как обеспечить постоянное использование емкости управляемой очереди, подобной (с точки зрения того, как ставить в очередь) структуре promises … Сегодня я многому научился.
5. Потрясающий ответ. Спасибо за обмен знаниями @trincot
Ответ №2:
Вот как вы можете сделать это без каких-либо внешних библиотек:
const ips = [
/* List of mac address. */
];
useEffect(() => {
const fetchData = async () => {
const loadedData = [];
// Iterate over the slices of array until all the ips have been processed.
for (const sliceIps of sliceGenerator(ips)) {
const gettingData = sliceIps.map(getDataFromIp);
const sliceLoadedData = await axios.all(gettingData);
loadedData = loadedData.concat(sliceLoadedData);
}
setData(loadedData);
};
fetchData();
}, []);
const getDataFromIp = (ip) =>
axios.get("/ipdn/<MAC ADDRESS>", { timeout: 10000 });
// Generates a slice of an array, here the slice has a size of 50 max.
function* sliceGenerator(arr) {
const sliceSize = 50;
let i = 0;
while (i < arr.length) {
yield arr.splice(i, i sliceSize);
i = sliceSize;
}
}
Здесь я использую генератор function* sliceGenerator
для генерации фрагментов массива ips. Таким образом, вы выполняете их пакетную обработку 50 на 50.
Я также использую for (... of ...)
цикл. Это очень удобно, потому что вы можете использовать await
ключевое слово внутри.
Комментарии:
1. Это потому, что я
await axios.all
в цикле2. Вы имели в виду использовать цикл while вместо
if
инструкции?3. @PrathapReddy это то, что я пытаюсь сказать, что его функция имеет только один выход, поэтому она выдаст только первое соединение. Он должен использовать цикл внутри функции генератора. Его функция выдаст массив с одним вложенным массивом длиной 50.
4. @RameshReddy, виноват. Этого не заметил. Он должен использовать цикл вместо
if
.5. Хороший момент, я не понял вашу точку зрения. Я обновил свой код
Ответ №3:
Если у вас нет проблем с использованием внешней библиотеки, вы можете использовать es6-promise-pool для управления параллельными запросами, как показано ниже
import PromisePool from 'es6-promise-pool';
// macs - Array of mac addresses
useEffect(() => {
const fetchData = () => {
const results = [];
const generatePromises = function*() {
for (let count = 0; count < macs.length; count ) {
yield axios.get(`/ipdn/${macs[count]}`, ...);
}
}
const promiseIterator = generatePromises();
// Create a pool with 10 concurrent requests max
const pool = new PromisePool(
promiseIterator,
10 // Configurable
);
// To listen to result
pool.addEventListener('fulfilled', function (event) {
console.log('Fulfilled: ' event.data.result);
results.push(event.data.result);
});
// Start the pool
pool.start().then(function () {
setData(results);
console.log('Complete');
});
};
fetchData();
}, []);
Комментарии:
1. Спасибо, Пратап. Не могли бы вы, пожалуйста, добавить какой-нибудь код, чтобы посмотреть, как включить это в useEffect.
2. Переместил логику внутрь
useEffect
и зафиксировал результат. Вы можете переместить его в массив и установить с помощьюsetData
после завершения выполнения пула. (Не добавлена обработка ошибок)3. Спасибо вам всем за отличные решения. Пратхап, я буду голосовать за ваш ответ, поскольку я использую этот.
4. Попробуйте также учесть комментарий @Bergi (7000 запросов от клиента), если есть какой-либо способ убедить вашего босса. (Учитывая медленное / ненадежное подключение к Интернету, мобильные сети и т.д.). Удачного кодирования 😄
5. зачем использовать async, если await не использовался?
Ответ №4:
Я не знаком с axios.all
, тогда я предоставляю способ просто использовать Promise.all
. Моя идея состоит в том, чтобы разделить входной массив на 50 адресов каждого блока, а затем решать его один за другим
function App() {
const [data, setData] = useState([]);
useEffect(() => {
// helper function, split array to chunked array
const splitToChunks = (items, chunkSize = 50) => {
const result = [];
for (let i = 0; i < items.length; i = chunkSize) {
result.push(items.slice(i, i chunkSize));
}
return resu<
}
const fetchData = async () => {
const result = []; // init value
const macAddresses = []; // array of mac addresses - your mac addresses
const chunkedArray = splitToChunks(macAddresses); // return array of array [[...50 mac adds], [], []]
for (const macs of chunkedArray) { // now macs is [...50 mac adds]
const promises = macs.map((mac) => {
return axios.get(`/ipdn/${mac}`, { timeout: 10000 });
});
// now promises is array contains 50 Promises
const response = await Promise.all(promises); // wait until finish 50 requests
result.push(...response); // copy response to result, and continue for next block
}
setData(result);
};
fetchData();
}, []);
}