Задание оставляет недосказанность — нужно ли просто ограничивать темп вызовов функции (и отклонять лишние), или же необходимо складывать все вызовы в очередь и выполнять их с некоторым темпом. А в случае очереди, нужно ли ожидать окончания выполнения предыдущего задания, или речь только о количестве вызовов?
Рассмотрим все варианты.
Содержание:
Ограничение темпа вызовов и reject лишних запросов
Самый хороший вариант для разнообразных интерфейсных функций — например, реакция на события mouseMove и подобные действия пользователя: передаём вызов функции некому обработчику, а он проверяет, сколько времени прошло после предыдущего вызова. Если прошло более N времени, вызываем функцию; если же нет, возвращаем отказ.
Видимо, именно о таком варианте шла речь в задании.
var CallController = function (interval) {
free = true;
this.rareCall = function (callback) {
if(free) {
free = false;
callback();
this.timer=setTimeout(function() { free = true; }, interval);
return true;
}
else return false;
}
}
var callController = new CallController(500);
callController.rareCall(function() { console.log('Вызвалось'); });
callController.rareCall(function() { console.log('Не вызвалось'); });
setTimeout(function() {
callController.rareCall(function() {console.log('Вызвалось, потому что прошёл таймаут'); });
}, 1000);
Два контроллера
Также, конечно, нас интересует ситуация с несколькими исполнительными функциями, имеющими разные периоды. К примеру, одна функция может 2 раза в секунду что-то обновлять в интерфейсе, а другая — раз в 5 секунд лазить на сервер и забирать какие-то новые данные. Или, если мы пишем чат — каждую секунду проверяем наличие новых сообщений, и каждую минуту — проверяем доступность остальных участников чата. Проверим работоспособность контроллера из прошлого листинга такими вызовами:
var callController1 = new CallController(500);
var callController2 = new CallController(700);
callController1.rareCall(function() { console.log('№1 Вызвалось'); });
callController2.rareCall(function() { console.log('№2 Вызвалось'); });
callController1.rareCall(function() { console.log('№1 Не вызвалось'); });
callController2.rareCall(function() { console.log('№2 Не вызвалось'); });
setTimeout(function() {
callController1.rareCall(function() {
console.log('№1 Вызвалось, потому что прошёл таймаут');
});
}, 1000);
setTimeout(function() {
callController2.rareCall(function() {
console.log('№2 Вызвалось, потому что прошёл таймаут');
});
}, 1500);
Очередь вызовов
Такой вариант хорошо подходит, например, для запросов к неким API, в т.ч. для уменьшения нагрузки на сервер API. Например, мне нужно было получить очень много разнообразной информации от Инстаграма, причём не просто подряд идущие страницы, а каждый раз что-то новое, в зависимости от содержания предыдущей загруженной страницы. Здесь нужно ожидать окончания предыдущего запроса.
Реализация такова: создаём очередь запросов — массив, каждый элемент которого является объектом с полями «url» и «callback». При обработке читаем первый элемент очереди, загружаем с помощью request заданный url.
По окончанию работы request, т.е. в его коллбеке, проверяем успешность выполнения запроса (функцию проверки успешности также можно передать в конструкторе). Если успешно — удаляем верхний (только что исполненный) элемент очереди и выполняем заданный коллбек, если нет — сообщаем об ошибке, а верхний элемент оставляем.
В любом случае, ставим таймаут на следующий шаг обработчика, и начинаем с начала.
Вариант с паузой после начала запроса отличается только тем, что таймаут ставим не в коллбеке request, а параллельно с ним, т.е. в функции чтения очереди.
function Pecker(interval) {
queue = { jobs: [] };
next=1;
this.request = function (callback) {
queue.jobs.push(callback);
this.process();
}
this.process = function (a) {
if(next==1) // флаг next означает, что можно выполнять следующее задание.
if(queue.jobs.length>0) {
next=0;
queue.jobs[0]();
queue.jobs.splice(0, 1);
}
}
setInterval( function (process) { return function() {
next=1;
process();
}}(this.process), interval);
};
var pecker = new Pecker(500);
pecker.request(function() { console.log('1 вызов'); });
pecker.request(function() { console.log('2 вызов'); });
pecker.request(function() { console.log('3 вызов'); });
К этому коду достаточно легко прикрутить vow/promises для определения, допустим, окончания всех операций, в том числе с передачей всех данных в обработчик vow.all.
В примере позволю себе реверанс в сторону Just for Fun Линуса Торвальдса.
Следующее задание (все подмассивы с суммой = 10)