Работа с пирамидой обратного вызова node.js

9

Я только начал использовать узел, и одну вещь, которую я быстро заметил, это то, как быстро обратные вызовы могут вырасти до глупого уровня отступа:

doStuff(arg1, arg2, function(err, result) {
    doMoreStuff(arg3, arg4, function(err, result) {
        doEvenMoreStuff(arg5, arg6, function(err, result) {
            omgHowDidIGetHere();
        });
    });
});

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

Можно ли использовать область действия, чтобы помочь здесь? Поместить все функции обратного вызова, которым нужен доступ к объекту global-ish, в функцию, которая объявляет этот объект, чтобы он перешел в замыкание?

function topLevelFunction(globalishObject, callback) {

    function doMoreStuffImpl(err, result) {
        doMoreStuff(arg5, arg6, function(err, result) {
            callback(null, globalishObject);
        });
    }

    doStuff(arg1, arg2, doMoreStuffImpl);
}

и так далее для еще нескольких слоев ...

Или существуют платформы и т. Д., Чтобы помочь уменьшить уровни отступов, не объявляя именованную функцию для каждого обратного вызова? Как вы справляетесь с пирамидой обратного вызова?

thecoop
источник
2
Обратите внимание на дополнительную проблему с замыканиями - в JS замыкание захватывает весь родительский контекст (в других языках он захватывает только используемые переменные или те, которые пользователь специально запрашивал), вызывая некоторые приятные утечки памяти, если иерархия обратного вызова достаточно глубока и, например, если обратный вызов сохраняется где-то.
Евгений,

Ответы:

7

Обещания обеспечивают четкое разделение проблем между асинхронным поведением и интерфейсом, поэтому асинхронные функции можно вызывать без обратных вызовов, а взаимодействие с обратным вызовом можно выполнять на универсальном интерфейсе обещаний.

Существует несколько реализаций «обещаний»:


Например, вы можете переписать это вложенные обратные вызовы

http.get(url.parse("http://test.com/"), function(res) {
    console.log(res.statusCode);
    http.get(url.parse(res.headers["location"]), function(res) {
        console.log(res.statusCode);
    });
});

подобно

httpGet(url.parse("http://test.com/")).then(function (res) {
    console.log(res.statusCode);
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);
});

Вместо обратного вызова в обратном вызове a(b(c()))вы связываете «.then» a().then(b()).then(c()).


Введение здесь: http://howtonode.org/promises

Фабьен Са
источник
Вы не могли бы объяснить больше о том, что делают эти ресурсы и почему вы рекомендуете их как ответы на заданный вопрос? «Ответы только на ссылки» не очень приветствуются на Stack Exchange
gnat
1
Хорошо извини Я добавил пример и больше информации.
Fabien Sa
3

В качестве альтернативы обещаниям вы должны взглянуть на yieldключевое слово в сочетании с функциями генератора, которые будут представлены в EcmaScript 6. Оба они доступны сегодня в сборках Node.js 0.11.x, но требуют, чтобы вы дополнительно указывали --harmonyфлаг при запуске Node .js:

$ node --harmony app.js

Использование этих конструкций и библиотеки, такой как TJ Holowaychuk's co, позволяет вам писать асинхронный код в стиле, который выглядит как синхронный код, хотя он все еще выполняется асинхронным способом. По сути, эти вещи вместе реализуют совместную поддержку Node.js.

По сути, вам нужно написать функцию генератора для кода, который выполняет асинхронные операции, вызвать там асинхронные операции, но поставить перед ними yieldключевое слово. Итак, в конце ваш код выглядит так:

var run = function * () {
  var data = yield doSomethingAsync();
  console.log(data);
};

Для запуска этой функции генератора вам нужна библиотека, такая как ранее упомянутая co. Звонок выглядит так:

co(run);

Или, чтобы поместить это в линию:

co(function * () {
  // ...
});

Обратите внимание, что из функций генератора вы можете вызывать другие функции генератора, все, что вам нужно сделать, это yieldснова поставить перед ними префикс .

Чтобы ознакомиться с этой темой, поищите в Google такие термины, как yield generators es6 async nodejsи вы должны найти тонны информации. Требуется некоторое время, чтобы привыкнуть к этому, но как только вы это получите, вы не захотите возвращаться никогда.

Обратите внимание, что это не только обеспечивает более приятный синтаксис для вызова функций, но также позволяет использовать обычные (синхронные) элементы логики потока управления, такие как forциклы или try/ catch. Больше не нужно возиться с множеством обратных вызовов и всем этим.

Удачи и приятного времяпровождения :-)!

Голо Роден
источник
0

Теперь у вас есть пакет asyncawait , с очень близкого синтаксиса к тому , что должно быть будущее нативная поддержка из await& asyncв узле.

По сути, это позволяет писать асинхронный код, выглядящий синхронно , резко снижая LOC и уровни отступов, с компромиссом небольшого снижения производительности (числа владельцев пакетов на 79% быстрее по сравнению с необработанными обратными вызовами), которые, как мы надеемся, будут уменьшены, когда будет доступна собственная поддержка.

Тем не менее, это хороший выбор, чтобы выбраться из ада обратного вызова / пирамиды обреченного кошмара IMO, когда производительность не является первостепенной задачей, и где стиль синхронной записи лучше соответствует потребностям вашего проекта.

Базовый пример из пакета документов:

var foo = async (function() {
    var resultA = await (firstAsyncCall());
    var resultB = await (secondAsyncCallUsing(resultA));
    var resultC = await (thirdAsyncCallUsing(resultB));
    return doSomethingWith(resultC);
});
Морозный Z
источник