Это популярная проблема, все пытаются с ней бороться, придумывают разные инструменты, библиотеки и прочее. Но скажу, что при хорошей архитектуре вложенность функций редко переваливает за 2 штуки - а это уже никакой не `хэл`. Ну вот посмотрите на метод в котором у вас этот самый `хэл`. Это же явное нарушение SOLID - один метод обрабатывает данные, куда-то их посылает, ждет в коллбэке ответ, с ним что-то делает, потом снова вызывает другую асинхронную функцию и опять ждет ответ, и так несколько раз. Поэтому `коллбэк хэл` - это классная штука, которая помогает разбивать код на независимые функции - принял данные, что-то сними сделал, отправил дальше как эти данные, так и другие аргументы. Точка.
Выше очень правильно заметили, что нужно разбивать методы на логические части поменьше, а не писать простыней, но еще есть проблема в том, что нужно сделать несколько запросов в БД или другим внешним источникам, к файлам, другим серверам, и потом на основе всех фрагментов данных, полученных асинхронно, исполнить бизнес-логику и сформировать ответ. Асинк прекрасно с этим справляется, как в примере ниже.
var dataRequests = [];
// массив запросов к разным данным (БД, файлы, сетевые вызовы)
var dataResults = {};
// я предпочитаю не использовать массив results, который дает async
// а вместо этого делать именованные фрагменты данных
// и писать их в свой хеш
// добавляем один запрос к данным
dataRequests.push(function(callback) {
db.queryRow(
'SELECT * FROM TableName1 where Field1=?', [someValue1],
function(err, res) {
dataResults.firstPiceName = res;
callback(err, null);
}
);
});
// добавляем второй запрос к данным
dataRequests.push(function(callback) {
db.queryValue(
'SELECT count(*) FROM TableName2 where Field2=?',
[someValue2],
function(err, res) {
dataResults.secondPiceName = res;
callback(err, null);
}
);
});
// вызываем массив запросов асинхронно
async.series(dataRequests, function(err, results) {
// тут делаем бизнес-логику над dataResults в котором у нас есть
// все фрагменты данных .firstPiceName и .secondPiceName
// и формируем общий результат
});
А еще рекомендую ставить тег "node.js" на вопросы по ноде.
Ну основные подходы вы перечислили.
За yield, генераторами и обертками над ними - будующее, но пока они доступны только в нестабильной ветке 0.11. Fibers - попытка их эмулировать, мне не очень нравится, да и смысл, если скоро генераторы появятся в стабильной ветке? Q и прочие промисы - хорошая штука, но в js по старой привычке использую async.
Текущие проекты пишу на LiveScript, в нем с последовательными асинхронными операциями помогает такая штука, как backcall'ы, пример.
Есть IcedCoffeeScript http://maxtaco.github.io/coffee-script/ почти (а это и есть) как кофе скрипт, только не вызывает рвотных позывов как LiveScript