Как известно, язык 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); //запуск первой функции }); }
Не уверен, что код и комментарии прозрачны, самому несколько секунд приходится вдумыватья :)
Для наглядности работы я сделал несколько последовательных анимаций:
Запустить пример на JSFidlewait(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('Закончили'); });
Как это работает?
Первым делом вызывается функция wait, аргументом которой служит другая функция, запускаемая сразу же с одним аргументом, служащим коллбеком (в примере он определен как runNext) для вызова следующей порции кода. После выполнения коллбека, в который можно передать некоторые аргументы, полученные на текущем шаге, вызывается следующая функция, переданная в метод wait, причем первым аргументом этой функции является коллбек, вызывающий следующую часть скрипта, остальные — аргументы, переданные в коллбек на предыдущем шаге. И так далее.
Собственно, всё.
P. S.
Судя по количеству добавивших статью в избранное, пост имеет некоторую ценность, поэтому, пожалуй, я не буду его прятать.
