Каков правильный способ подключения асинхронных вызовов в javascript?

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

Это то, что я сейчас делаю.

var syncProduct = (function() {
    var done, log;
    var IN_CAT = 1, IN_TITLES = 2, IN_BINS = 3;
    var state = IN_CAT;
    var processNext = function(data) {
        switch(state) {
            case IN_CAT:
                SVC.sendJsonRequest(url("/api/lineplan/categories"), processNext);
                state = IN_TITLES;
                break;
            case IN_TITLES:
                log((data ? data.length : "No") + " categories retrieved!");
                SVC.sendJsonRequest(url("/api/lineplan/titles"), processNext);
                state = IN_BINS;
                break;
            case IN_BINS:
                log((data ? data.length : "No") + " titles retrieved!");
                SVC.sendJsonRequest(url("/api/lineplan/bins"), processNext);
                state = IN_MAJOR;
                break;
            default:
                log((data ? data.length : "No") + " bins retrieved!");
                done();
                break;
        }
    }
    return {
        start: function(doneCB, logCB) {
            done = doneCB; log = logCB; state = IN_CAT;
            processNext();
        }
    }
})();

Тогда я бы назвал это следующим образом

var log = function(message) {
    // Impl removed.
}

syncProduct.start(function() {
    log("Product Sync Complete!");
}, log);

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

ПРИМЕЧАНИЕ. Я не использую javascript в браузере, но изначально в рамках Titanium, это похоже на Javascript для Node.js.

Ответ 1

Существует множество библиотек и инструментов, которые выполняют асинхронную цепочку и поток управления для вас, и они в основном представлены в двух основных вариантах:

  • Библиотеки потока управления

    Например, см. async, seq и step (на основе обратного вызова) или Q и futures (на основе обещаний). Основным преимуществом этого является то, что они являются просто равными JS-библиотеками, которые облегчают боль при асинхронном программировании.

    В моем личном опыте, библиотеки на основе обещаний, как правило, приводят к коду, который больше похож на обычный синхронный код, поскольку вы возвращаете значения с использованием "return", и поскольку значения обещаний могут передаваться и храниться вокруг, аналогично действительным значениям.

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

  • Javascript CPS-компиляторы

    Расширение языка для добавления встроенной поддержки для сопроводителей/генераторов позволяет вам писать асинхронный код очень простым образом и хорошо играть с остальной частью языка, что означает, что вы можете использовать Javascript, если инструкции, циклы и т.д. вместо необходимости их репликации функции. Это также означает, что его очень легко преобразовать ранее синхронизирующий код в асинхронную версию. Тем не менее, существует очевидный недостаток, заключающийся в том, что не каждый браузер будет запускать ваше Javascript-расширение, поэтому вам нужно будет добавить шаг компиляции в ваш процесс сборки, чтобы преобразовать ваш код в обычный JS с обратными вызовами в стиле продолжения-прохождения. Во всяком случае, одна из перспективных альтернатив - генераторы в спецификации Ecmascript 6, в то время как только firefox поддерживает их изначально, существуют такие проекты, как regenerator и Traceur, чтобы скомпилировать их обратно к обратным вызовам. Существуют и другие проекты, которые создают свой собственный синтаксис асинхронного (так как генераторы es6 тогда не возвращались). В этой категории вы найдете такие вещи, как tamejs и Iced Coffeescript. Наконец, если вы используете Node.js, вы также можете взглянуть на Fibers.


Рекомендация:

Если вам просто нужно что-то простое, что не усложнит процесс сборки, я бы рекомендовал, чтобы вся библиотека управления потоком соответствовала вашему личному стилю и библиотекам, которые вы уже используете.

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