Итак, мы пишем приложение с кучей асинхронных запросов. Нам надо отправить два асинхронных запроса и обработать их результат только после того, как будет получен результат обоих. Например, это могут быть ассинхронные обращение к файлу и запрос к базе, результат которых надо сложить вместе и обработать. Или два аджакс запроса.
Но особенность асинхронных запросов в том, что мы не знаем, какой из них придёт первым, а какой — последним. Решают это разными способами, но я не видел еще красивого и изящного. В топике я расскажу, как я это вижу.
Обычно поступают по одному из следующих вариантов:
Обычно такое могут сделать только люди, которые совершенно не понимают ассинхронные вызовы. Как-то так:
Представим, что обращение с файловой системой чуть затормозилось и запрос от базы пришёл быстрее. Тогда в fileData ничего не будет, хотя мы просто не длождались этих данных! Печально…
Другой вариант — как минимум надежный. Совершаем один запрос, ждем пока он завершится, совершаем второй запрос.
Это лучше, чем надежда на лучшее, но тут мы жертвуем временем — база бы могла уже искать, пока происходит обращение к файловой системе, а нет — приходится сидеть, сложа руки, ждать. А, в случае с аджаксом, пользователь будет ждать ответ в два раза дольше. Нехорошо
Система флагов или проверка переменных. После каждого запроса отмечаем флаг, соответствующий ему и, если все флаги отмечены — выполняем. самый правильный вариант.
Но не очень красивый. Код повторяется, флаги надо дописывать во много мест. Если надо послать три одновременных запроса — будет дофига текст
Так лучше, но тоже не очень приятно. Флаги повторяются 4 раза (посчитайте количество переменных fileData)
Код, в результате, такой:
Или, даже, такой:
По сути мы воспользуемся вариантом с флагами, но абстрагируем его под методом, который добавим в прототип функции:
Думаю, это достаточно удобный и красивый способ для такой редкой, но важной задачи.
Но особенность асинхронных запросов в том, что мы не знаем, какой из них придёт первым, а какой — последним. Решают это разными способами, но я не видел еще красивого и изящного. В топике я расскажу, как я это вижу.
var process = processFsAndDb.after('fs', 'db'); asyncFsAccess( file, process.fs); asyncDbAccess(query, process.db);
Обычно поступают по одному из следующих вариантов:
Авось повезет
Обычно такое могут сделать только люди, которые совершенно не понимают ассинхронные вызовы. Как-то так:
var fileData = null;
asyncFsAccess( file, function (data) {
fileData = data;
});
asyncDbAccess(query, function (dbData) {
processFsAndDb(fileData, dbData);
});
Представим, что обращение с файловой системой чуть затормозилось и запрос от базы пришёл быстрее. Тогда в fileData ничего не будет, хотя мы просто не длождались этих данных! Печально…
Фиг с ним, подожду
Другой вариант — как минимум надежный. Совершаем один запрос, ждем пока он завершится, совершаем второй запрос.
asyncFsAccess( file, function (fileData) {
asyncDbAccess(query, function (dbData) {
processFsAndDb(fileData, dbData);
});
});
Это лучше, чем надежда на лучшее, но тут мы жертвуем временем — база бы могла уже искать, пока происходит обращение к файловой системе, а нет — приходится сидеть, сложа руки, ждать. А, в случае с аджаксом, пользователь будет ждать ответ в два раза дольше. Нехорошо
7 раз проверь
Система флагов или проверка переменных. После каждого запроса отмечаем флаг, соответствующий ему и, если все флаги отмечены — выполняем. самый правильный вариант.
var fileData = dbData = null;
asyncFsAccess( file, function (data) {
fileData = data;
if (fileData && dbData)
processFsAndDb(fileData, dbData);
});
asyncDbAccess(query, function (data) {
dbData = data;
if (fileData && dbData)
processFsAndDb(fileData, dbData);
});
Но не очень красивый. Код повторяется, флаги надо дописывать во много мест. Если надо послать три одновременных запроса — будет дофига текст
7 раз проверь ++
var fileData = dbData = null;
var process = function () {
if (fileData && dbData)
processFsAndDb(fileData, dbData);
};
asyncFsAccess( file, function (data) {
fileData = data;
process();
});
asyncDbAccess(query, function (data) {
dbData = data;
process();
});
Так лучше, но тоже не очень приятно. Флаги повторяются 4 раза (посчитайте количество переменных fileData)
Мой вариант, расширяем прототип
Код, в результате, такой:
var process = function (args) {
processFsAndDb(args.fs, args.db);
}.after('fs', 'db');
asyncFsAccess( file, process['fs']);
asyncDbAccess(query, process['db']);
Или, даже, такой:
var process = processFsAndDb.after('fs', 'db');
asyncFsAccess( file, process.fs);
asyncDbAccess(query, process.db);
По сути мы воспользуемся вариантом с флагами, но абстрагируем его под методом, который добавим в прототип функции:
Function.prototype.after = function () {
/**
* @var onReady - это наша ф-ция, которая будет выпонлятся по завершению всех вызовов
* @var after - это хэш функций, которые должны быть выполнены.
* именно его свойства мы передаём как колбеки (process.fs и process.db)
* @var ready - здесь мы будем хранить ответы каждой функции и когда все ключи,
* которые есть в after будут и тут - значит, пора вызывать onReady
*/
var onReady = this, after = {}, ready = {};
var checkReady = function () {
for (var i in after) if (!(i in ready)) return;
onReady(ready);
};
for (var i = 0, l = arguments.length; i < l; i++) {
(function (key) {
after[key] = function () {
ready[key] = arguments;
checkReady();
};
})(arguments[i]);
}
return after;
};
Думаю, это достаточно удобный и красивый способ для такой редкой, но важной задачи.