Javascript-форум (https://javascript.ru/forum/)
-   Элементы интерфейса (https://javascript.ru/forum/dom-window/)
-   -   Как реализовать задачу с одновременными запросами на сервер? (https://javascript.ru/forum/dom-window/81490-kak-realizovat-zadachu-s-odnovremennymi-zaprosami-na-server.html)

dc65k 05.12.2020 17:08

Как реализовать задачу с одновременными запросами на сервер?
 
Всем привет, есть задача со следующими условиями:
1. Написать функцию, получающую на вход массив ссылок и число, указывающее максимальное количество одновременных запросов.
2. Одновременно должно выполняться не более указанного числа запросов.
3. Должен возвращаться массив результатов в той же последовательности, что и адреса запросов.
4. Нельзя делать повторные запросы на дублирующиеся адреса (при этом результат всё равно должен присутствовать в результирующем массиве).
makeRequest([
    'url1',
    'url2',
    'url1',
    'url3'
], 2).then(([
	    result1,
	    result2,
	    result1,
	    result3
	]) => {
});

Итак, моё решение:
const removingDuplicates = (arr) => {
    return arr.filter((item, i, arr) => arr.indexOf(item) === i)
}

function makeRequests (urls, maxNumberRequests) {
    const uniqueUrls = removingDuplicates(urls);
    // console.log(uniqueUrls);

    let response = null;

    uniqueUrls.forEach(async (url, idx) => {
        // console.log('url', url, idx);

        if (idx === maxNumberRequests) {
            return false;
        }

        response = await fetch(url, {
            method: 'POST',
            body: "statistics",
            keepalive: true
        });
    });

    return response;
}

makeRequests([
    'url1',
    'url2',
    'url1',
    'url3'
], 2);

Собственно, подскажите, что мне нужно подправить, полагаю, что я не совсем правильно понял условие задачи.

Nexus 05.12.2020 17:18

Имхо, у вас реализация удовлетворяет только первому условию, остальные мимо.

Цитата:

Сообщение от dc65k
2. Одновременно должно выполняться не более указанного числа запросов.

Из-за await у вас единовременно будет выполняться всегда только 1 запрос, не более. А из-за 14-й строки выполниться вообще не более «maxNumberRequests» запросов, остальные будут проигнорированы.

Цитата:

Сообщение от dc65k
Должен возвращаться массив результатов в той же последовательности, что и адреса запросов.

У вас вообще ничего не возвращается. Даже если возвращать переменную response, то вернется только результат последнего запроса.

Цитата:

Сообщение от dc65k
4. Нельзя делать повторные запросы на дублирующиеся адреса (при этом результат всё равно должен присутствовать в результирующем массиве).

Вы удаляете все дубликаты из массива «urls», это ок, но не учитываете, что результат для этих дубликатов "всё равно должен присутствовать в результирующем массиве".

voraa 05.12.2020 23:30

Я попытался написать.
У меня работало.
Использовал GET вместо POST для удобства.
Может сложновато получилось, но как есть

const getpreq = async (areq, maxreq) => {
	const mres = new Map(); // Набор запросов, выполняемых в данный момент url => индекс
	const pres = [];  // Массив, в который записываются обещания выполнения запросов 
					// В виде {responce: Promise (выполнения запроса для уникальных)
					// Или {index: номер предыдущего запроса, для повторяющихся

// Функция ожидает, когда можно быдет выполнять следующий запрос
	const mayaddreq = async () => {
		if (mres.size < maxreq) return true;
		// Формируем массив обещаний из выполняющихся запросов и ждем, когда какой-нибудь выполнится
		await Promise.any([... mres.values()].map (ind => pres[ind]))
		return true;
	}

// Добавляет новый запрос	
	const addreq = (i, url) => {

		let p = fetch(url, {  // Обещание выполнение запроса
            method: 'GET',
//            body: "statistics",
//            keepalive: true
        })
        let mp = p.then (res => {  // Кошда запрос выполнится
			mres.delete(url);      // Удалить его из списка выполняемых
			return {responce:res}  // Вернуть ответ
        })
        mres.set (url, i)   // Записать в список выполняемых
        return mp        // Возвращаем обещание
	}
	
	
	for (let i= 0; i< areq.length; i++) {
		const url = areq[i];
		let si = areq.indexOf(url)
		if (si>=0 && si < i) {      // Это повторяющийся запрос
			pres.push( {index:si})
		} else {
			await mayaddreq ()     //  Ждем, когда можно начать выполнять
			pres.push (addreq (i, url)) // Выполняем запрос и записываем обещание в массив
		}
	}
	return pres
}

// Функция получает массив вида [{responce: ответ responce от fetch} или {index: номер исходного responce для повторяющихся} ...]
// Возвращает массив результатов обработки responce
const getans = async (aresp) => {
	let aansp = []   // массив обещаний обработки responce
	aresp.forEach ((r, i, ar) => {     // в данном случае я использую responce.text
		aansp[i] = r.responce? r.responce.text() : Promise.resolve(aansp[r.index])
	})
		
	return Promise.all (aansp)  
}

aurls = [
'testaj.php?num=1&val=10',
'testaj.php?num=2&val=20',
'testajm.php?num=3&val=30',
'testajm.php?num=4&val=40',
'testaj.php?num=5&val=50',
'testajm.php?num=6&val=60',
'testaj.php?num=7&val=70',
'testajm.php?num=6&val=60',
'testaj.php?num=2&val=20',
'testajm.php?num=6&val=60',
'testajm.php?num=3&val=30',
'testajm.php?num=8&val=80',
'testajm.php?num=8&val=80',
'testajm.php?num=8&val=80',
'testaj.php?num=9&val=90',
'testaj.php?num=1&val=10',
]

const main = async () => {
	let prec = await getpreq (aurls, 5) 
	let aresp = await Promise.all (prec)
	let ans = await getans (aresp)
	console.log (ans)
}

main()


Зы Предполагается, что запросы выполняются без ошибок.
С обработкой ошибок сложнее будет.

Alexandroppolus 06.12.2020 02:57

такие штуковины надо делать в параметризуемом виде.
вот, с обработкой ошибок

// запускатель асинхронной функции для параметров
/*
args: {
  request: param => Promise
  getKey: param => value
  limit: number
  params: array
}
return [...]
*/
function runRequests(args) {
  if (!args.params) {
    return Promise.resolve([]);
  }
  return new Promise(res => {
    const results = new Array(args.params.length);
    const map = new Map();
    const req = []
    for (let i = 0; i < args.params.length; ++i) {
      const param = args.params[i];
      const key = args.getKey ? args.getKey(param) : param;
      let config = map.get(key);
      if (!config) {
        config = {
          param: param,
          indexes: [i]
        };
        map.set(key, config);
        req.push(config);
      } else {
        config.indexes.push(i);
      }
    }
    const limit = Math.min(args.limit || req.length, req.length);
    let pos = 0;
    let doneCount = 0;

    function run() {
      if (pos < req.length) {
        const config = req[pos++];
        args.request(config.param).then(
          data => done(false, data, config),
          error => done(true, error, config))
      }
    }

    function done(error, value, config) {
      // результат в комплексном виде, тут может быть как успешно, так и с ошибкой
      const item = {
        success: !error,
        data: error ? undefined : value,
        error: error ? value : undefined
      };
      for (let i = 0; i < config.indexes.length; ++i) {
        results[config.indexes[i]] = item
      }
      doneCount++;
      if (doneCount >= req.length) {
        res(results);
      } else {
        run();
      }
    }

    for (let i = 0; i < limit; ++i) {
      run();
    }
  });
}

// пример асинхронной функции, которая получает параметр и возвращает промис
function asyncReverse(value) {
  console.log('request ', value);
  return new Promise((res, rej) => {
    const r = Math.random();
    const delay = Math.floor(2000 + 3000 * Math.random());
    setTimeout(() => {
      if (r < 0.00002) {
        rej(r);
      } else {
        console.log('result for ', value);
        res(value.split('').reverse().join(''));
      }
    }, delay);
  });
}

// запуск для параметров
runRequests({
  request: asyncReverse, // асинхронная функция
  getKey: param => param, // ключ, по которому выявляем одинаковые параметры
  limit: 3,
  params: [
    '1234',
    'qazx',
    'rwsxc',
    'edcv',
    'rfvb',
    'edcv'
  ]
}).then(console.log)


смотреть в консоли результат можно

dc65k 06.12.2020 12:14

Всем спасибо за помощь.


Часовой пояс GMT +3, время: 22:18.