Как дождаться выполнения асинхронных функций

Состояние, когда такое надобится попахивает промахом в проектировании (или специфическими задачами). Однако проблема есть и требует решения.

Идея

Создать обертки для асинхронных функций. Эти обертки будут проверять состояние выполнения других оберток. Когда желаемое состояние достигается, вызывается главный коллбек.

Реализация

Обертки создаются с помощью декорирования коллбеков. Ключом к набору асинхронных функций и их состояний будет функция коллбек (реализуется через два массива). Смотреть можно на гитхабе или ниже, но с комментариями.
function decorate (initial, decorate_before, decorate_after) {
	return function () {
		var initial_call_result;

		if (typeof decorate_before === 'function') {
			if (!!decorate_before.apply(this, arguments) === false) {
				return;
			}
		}
		initial_call_result = initial.apply(this, arguments);
		if (typeof decorate_after === 'function') {
			decorate_after.apply(this, arguments);
		}
		return initial_call_result;
	};
}


function chain (func, callback) {
	var callbacks,
		func_states;

	// Массив для коллбеков. Индекс коллбека соответствует
	// индексу набора состояний
	callbacks = [];
	// Состояния асинхронных функций
	func_states = [];
	chain = function (func, callback) {
		var callback_index,
			chained,
			func_index;

		// получаем объект состояний
		callback_index = callbacks.indexOf(callback);
		if (callback_index === -1) {
			// если его нет, создаем новый
			chained = {
				func : [],
				state : [],
				// если все функции выполнились, запускаем коллбек
				test : function () {
					for (var i = 0; i < this.func.length; i += 1) {
						if (!this.state[i]) {
							return;
						}
					}
					callback();
					this.state = [];
				}
			};
			callbacks.push(callback);
			func_states.push(chained);
		} else {
			chained = func_states[callback_index];
		}

		// оборачиваем функцию, чтобы она обноавляла состояние
		func_index = chained.func.push(func) - 1;
		func = decorate(func, null, function () {
			chained.state[func_index] = true;
			chained.test();
		});
		return func;
	};
	return chain.apply(this, arguments);
}
И пример использования:
function on_all_async_ready () {
	console.log(‘all ready’);
}

var on_2_of_3_ready = function () {
	console.info(‘1 and 2 ready’);
}

var async_1 = chain(function () {
	console.info(‘first async’);
}, on_all_async_ready);

async_1 = chain(async_1, on_2_of_3_ready);


var async_2 = chain(function () {
	console.info(‘second_async’);
}, on_all_async_ready);

async_2 = chain(async_2, on_2_of_3_ready);


var async_3 = chain(function () {
	console.info(‘third async’);
}, on_all_async_ready);


setTimeout(function () {
	async_1();
	setTimeout(async_2, 1000);
	setTimeout(async_3, 2000);
}, 2000);
Share
Send
Popular