Как известно, язык JavaScript преследует парадигму событийно-ориентированного программирования. Это, безусловно, хорошо, но что делать, если за одной асинхронной функцией должна вызываться другая асинхронная функция, а затем еще одна, и еще… Иногда такой код очень запутывает, и не только человека привыкшего к синхронному и поочередному вызову функций. Это касается сложных анимаций, таймаутов, аякса, когда за одним должно следовать другое, и так дальше.
Поэтому, я разработал свой костыль, который позволяет более наглядно вызывать асинхронные функции, запускающие callback после выполнения. Вполне вероятно, что решение уже давно существует, но я, к сожалению, такого решения не нашел.
UPD

Ниже моё решение, являющееся аналогом этой функции модуля async и кучи других подобных решений, представленных в комментариях. Спасибо всем комментирующим и sedictor в частности.
/UPD
Рассмотрим пример (который взят из головы и в нем возможны ошибки) гипотетического парсера сайта, который после парсинга заносит данные в БД, и, после занесения, вызывает некоторый код.
Много вложенных колбеков — не есть гуд, пробуем по-другому.
Но здесь несколько лишних переменных, в которых так же можно запутаться.
Я предлагаю сделать вот так:
Интересно? Поехали дальше.
Функция wait.
Не уверен, что код и комментарии прозрачны, самому несколько секунд приходится вдумыватья :)
Для наглядности работы я сделал несколько последовательных анимаций:
Как это работает?
Первым делом вызывается функция wait, аргументом которой служит другая функция, запускаемая сразу же с одним аргументом, служащим коллбеком (в примере он определен как runNext) для вызова следующей порции кода. После выполнения коллбека, в который можно передать некоторые аргументы, полученные на текущем шаге, вызывается следующая функция, переданная в метод wait, причем первым аргументом этой функции является коллбек, вызывающий следующую часть скрипта, остальные — аргументы, переданные в коллбек на предыдущем шаге. И так далее.
Собственно, всё.
P. S.Как писалось выше, я не уверен в уникальности такой разработки, поэтому, если Хабрасообщество оценит эту краткую статью как банальность, она тихонечко отправится в черновики.
Судя по количеству добавивших статью в избранное, пост имеет некоторую ценность, поэтому, пожалуй, я не буду его прятать.
Поэтому, я разработал свой костыль, который позволяет более наглядно вызывать асинхронные функции, запускающие callback после выполнения. Вполне вероятно, что решение уже давно существует, но я, к сожалению, такого решения не нашел.
UPD

Ниже моё решение, являющееся аналогом этой функции модуля async и кучи других подобных решений, представленных в комментариях. Спасибо всем комментирующим и sedictor в частности.
/UPD
Рассмотрим пример (который взят из головы и в нем возможны ошибки) гипотетического парсера сайта, который после парсинга заносит данные в БД, и, после занесения, вызывает некоторый код.
var html = '';
request.on('response', function (response) {
response.on('data', function (chunk) {
html = html + chunk;
});
response.on('end', function() {
//какой-то парсер
parse(html, function(data){
//какая-нибудь функция, добавляющая данные в базу
addToDatabase(data, function() {
doSomething();
})
});
});
});
Много вложенных колбеков — не есть гуд, пробуем по-другому.
var html = '';
var responceOnEnd = function() {
parse(html, parsed);
}
var parsed = function(data){
addToDatabase(data, addedToDatabase)
}
var addedToDatabase = function() {
doSomething();
}
request.on('response', function (response) {
response.on('data', function (chunk) {
html = html + chunk;
});
response.on('end', responceOnEnd);
});
Но здесь несколько лишних переменных, в которых так же можно запутаться.
Я предлагаю сделать вот так:
wait(function(runNext){
request.on('response', runNext);
}).wait(function(runNext, response){
response.on('data', function (chunk) {
html = html + chunk;
});
response.on('end', function() {
runNext(html);
});
}).wait(function(runNext, html){
parse(html, runNext);
}).wait(function(runNext, data){
addToDatabase(data, runNext);
}).wait(function(){
doSomething();
})
Интересно? Поехали дальше.
Функция wait.
//first — первая функция,которую нужно запустить
wait = function(first){
//класс для реализации вызова методов по цепочке
return new (function(){
var self = this;
var callback = function(){
var args;
if(self.deferred.length) {
/* превращаем массив аргументов
в обычный массив */
args = [].slice.call(arguments);
/* делаем первым аргументом функции-обертки
коллбек вызова следующей функции */
args.unshift(callback);
//вызываем первую функцию в стеке функций
self.deferred[0].apply(self, args);
//удаляем запущенную функцию из стека
self.deferred.shift();
}
}
this.deferred = []; //инициализируем стек вызываемых функций
this.wait = function(run){
//добавляем в стек запуска новую функцию
this.deferred.push(run);
//возвращаем this для вызова методов по цепочке
return self;
}
first(callback); //запуск первой функции
});
}
Не уверен, что код и комментарии прозрачны, самому несколько секунд приходится вдумыватья :)
Для наглядности работы я сделал несколько последовательных анимаций:
wait(function(runNext){
log('Первая анимация пошла');
$('#div1').animate({
top: 30
}, 1000, function(){
//передаем какие-нибудь аргументы в следующий вызов
runNext(1,2);
});
}).wait(function(runNext, a, b){
//используем аргументы из предыдущего вызова
log('Вторая анимация пошла, a='+a+' b='+b );
$('#div2').animate({
top: 50
}, 1000, runNext);
}).wait(function(runNext){
log('Ждем две секунды');
setTimeout(function(){
log('Две секунды прошли')
runNext();
}, 2000);
}).wait(function(runNext){
log('Третья анимация пошла');
$('#div3').animate({
left: 50
}, 1000, runNext);
}).wait(function(runNext){
log('Последняя анимация');
$('#div1').animate({
top: 0,
left: 45
}, 1000, runNext);
}).wait(function(){
log('Закончили');
});
Запустить пример на JSFidleКак это работает?
Первым делом вызывается функция wait, аргументом которой служит другая функция, запускаемая сразу же с одним аргументом, служащим коллбеком (в примере он определен как runNext) для вызова следующей порции кода. После выполнения коллбека, в который можно передать некоторые аргументы, полученные на текущем шаге, вызывается следующая функция, переданная в метод wait, причем первым аргументом этой функции является коллбек, вызывающий следующую часть скрипта, остальные — аргументы, переданные в коллбек на предыдущем шаге. И так далее.
Собственно, всё.
P. S.
Судя по количеству добавивших статью в избранное, пост имеет некоторую ценность, поэтому, пожалуй, я не буду его прятать.