Подтвердить что ты не робот

Как избежать жестко закодированных асинхронных функций с цепочкой в ​​Javascript/jQuery?

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

function getStuff() {
    $.ajax({
        ...
        success: function(results) {
            // other functions involving results
            getMoreStuff(results);
        }
    });
}

function getMoreStuff(results) {
    $.ajax({
        ...
        success: function(moreResults) {
            // other functions involving moreResults
            doSomethingWithStuff(moreResults);
        }
    );
}

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

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

getStuff(function() {
    getMoreStuff(results, doSomethingWithStuff);
};

Но тогда "результаты" еще не определены.

Решение кажется очевидным, я просто немного плотно об этом. Извините!

4b9b3361

Ответ 1

Обзор

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

getStuff(function(results) {
    getMoreStuff(results, doSomethingWithStuff);
});

или как это, используя объекты jQuery Deferred и Promise:

getStuff().then(getMoreStuff).then(doSomethingWithStuff):

Использование обратных вызовов

У обоих getStuff и getMoreStuff принять аргумент, который является обратным вызовом для вызова, когда они будут выполнены, например:

function getStuff(callback) {
//                ^------------------------------ callback argument
    $.ajax({
        ...
        success: function(results) {
            // other functions involving results
            callback(results);
//          ^------------------------------------ use the callback arg
        }
    });
}

... и аналогично для getMoreStuff.

Использование Deferred и Promise

Функция

jQuery ajax интегрируется с ее функциями Deferred и Promise. Вы можете просто добавить return к своим существующим функциям, чтобы выполнить эту работу, например:

function getStuff(callback) {
    return $.ajax({
        ...
    });
}

(Примечание: нет необходимости в обратном вызове success.)

Затем этот код:

getStuff().then(getMoreStuff).then(doSomethingWithStuff);

делает следующее:

  • getStuff запускает вызов ajax и возвращает Promise, который вызывает вызов.

  • Когда вызов ajax завершает и разрешает обещание, в качестве первого аргумента вызывается getMoreStuff с результатами вызова ajax. Он начинает свой вызов ajax.

  • Когда вызов getMoreStuff ajax завершается, doSomethingWithStuff вызывается с результатами этого вызова (тот, что находится в getMoreStuff).

Важно использовать then, а не done, чтобы получить правильные результаты, переданные на каждом этапе. (Если вы используете done, то как getMoreStuff , так и doSomethingWithStuff будут отображаться результаты вызова getStuff ajax.)

Здесь приведен полный пример с использованием ajax:

Fiddle | Alternate Fiddle с вызовами ajax, занимающих одну секунду каждый (упрощает просмотр того, что происходит)

function getStuff() {
    display("getStuff starting ajax")
    return $.ajax({
        url: "/echo/json/",
        type: "POST",
        data: {json: '{"message": "data from first request"}'},
        dataType: "json"
    });
}

function getMoreStuff(results) {
    display("getMoreStuff got " + results.message + ", starting ajax");
    return $.ajax({
        url: "/echo/json/",
        type: "POST",
        data: {json: '{"message": "data from second request"}'},
        dataType: "json"
    });
}

function doSomethingWithStuff(results) {
    display("doSomethingWithStuff got " + results.message);
}

getStuff().then(getMoreStuff).then(doSomethingWithStuff);

function display(msg) {
    var p = document.createElement('p');
    p.innerHTML = String(msg);
    document.body.appendChild(p);
}

Вывод:

getStuff starting ajax

getMoreStuff got data from first request, starting ajax

doSomethingWithStuff got data from second request

Вам не нужно использовать ajax, чтобы получить выгоду от этого, вы можете использовать свои собственные объекты Deferred и Promise, которые позволяют писать такие цепочки:

one().then(two).then(three);

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

Здесь пример не ajax:

Fiddle

function one() {
    var d = new $.Deferred();
    display("one running");
    setTimeout(function() {
      display("one resolving");
      d.resolve("one");
    }, 1000);
    return d.promise();
}

function two(arg) {
    var d = new $.Deferred();
    display("Two: Got '" + arg + "'");
    setTimeout(function() {
      display("two resolving");
      d.resolve("two");
    }, 500);
    return d.promise();
}

function three(arg) {
    var d = new $.Deferred();
    display("Three: Got '" + arg + "'");
    setTimeout(function() {
      display("three resolving");
      d.resolve("three");
    }, 500);
    return d.promise();
}

one().then(two).then(three);

function display(msg) {
    var p = document.createElement('p');
    p.innerHTML = String(msg);
    document.body.appendChild(p);
}

Вывод:

one running

one resolving

Two: Got 'one'

two resolving

Three: Got 'two'

three resolving

Эти два (пример ajax и пример не ajax) могут быть объединены при необходимости. Например, если мы возьмем getStuff из примера ajax, и мы решили, что нам нужно выполнить некоторую обработку данных, прежде чем передать его на getMoreStuff, мы бы изменили его следующим образом: Fiddle

function getStuff() {
    // Create our own Deferred
    var d = new $.Deferred();
    display("getStuff starting ajax")
    $.ajax({
        url: "/echo/json/",
        type: "POST",
        data: {json: '{"message": "data from first request"}', delay: 1},
        dataType: "json",
        success: function(data) {
            // Modify the data
            data.message = "MODIFIED " + data.message;

            // Resolve with the modified data
            d.resolve(data);
        }
    });
    return d;
}

Обратите внимание, что мы не используем это:

getStuff().then(getMoreStuff).then(doSomethingWithStuff);

Все, что изменилось, находилось в пределах getStuff.

Это одна из величайших вещей по всей концепции "обещания" (которая совсем не специфична для jQuery, но jQuery дает нам удобные версии), это фантастика для развязывания вещей.

Ответ 2

Try

function getStuff() {
    return $.ajax({
        ...
        success: function(results) {
            // other functions involving results
        }
    });
}

function getMoreStuff(results) {
    return $.ajax({
        ...
        success: function(moreResults) {
            // other functions involving moreResults
        }
    );
}

Тогда

getStufff().done(function(){
    getMoreStuff().done(doSomethingWithStuff)
})

и т.д.

Ответ 3

Передайте обратные вызовы, которые принимают параметр:

function getStuff( callback ) {
    $.ajax({
        ...
        success: function(results) {
            // callback with result
            callback(results);
        }
    });
}

function getMoreStuff(results, callback) {
    $.ajax({
        ...
        success: function(moreResults) {
            // callback with result
            callback(moreResults);
        }
    );
}

function doSomethingWithStuff(results, callback) {
    // process results via some means not described herein :)
    if (callback){
        // callback yet again with results, but only if callback provided this time
        callback(stillMoreResults);
    }
}

Затем используйте что-то вроде:

getStuff(function(results) { 
    getMoreStuff(results, function(moreresults){
             doSomethingWithStuff(moreresults);
        });
    };

Этот шаблон обычно полезен для любых асинхронных операций. Это не относится к вызовам Ajax (я использовал его для создания полной анимированной настольной игры в JQuery).

Ответ 4

Решение очень простое. Вы должны использовать шаблон Publish–subscribe. Простейшая реализация с помощью jQuery:

$('body').trigger('joined-game', [game_id, response]);

Первый аргумент - это имя события, которое вы публикуете, вторым аргументом является массив данных.

Лучшей практикой является запуск события на самом конкретном элементе DOM, но если вы подписываете одно и то же событие на нескольких страницах и не уверены, присутствует ли элемент DOM на всех страницах, вы можете запустить его на body или некоторый "дамп/синтетический" невидимый элемент DOM всегда присутствует на всех страницах.

$("body").on('joined-game', function(event, game_id, response){
    //...
});

Затем вы подписываетесь на мероприятие, которое хотите использовать. Помните, что помимо ваших данных первым аргументом всегда является событие.

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

Подробнее: http://webility.pl/en/blog/entry/chaining-javascript-functions-without-dependecy-hell