Задание оставляет недосказанность — нужно ли просто ограничивать темп вызовов функции (и отклонять лишние), или же необходимо складывать все вызовы в очередь и выполнять их с некоторым темпом. А в случае очереди, нужно ли ожидать окончания выполнения предыдущего задания, или речь только о количестве вызовов?

Рассмотрим все варианты.

Ограничение темпа вызовов и 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)