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

Как выполнить функцию Javascript только после выполнения нескольких других функций?

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

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

funcA = function() {
    //Does funcA stuff
    funcB();
}
funcB = function() {
    //Does funcB stuff
    funcC();
}

funcA();

Я даже мог сделать этот шаблон более общим, передав параметры обратного вызова, однако это решение становится довольно многословным.

Я также знаком с цепочкой функций Javascript, где может выглядеть решение:

myObj = {}
myObj.answer = ""
myObj.funcA = function() {
    //Do some work on this.answer
    return this;
}
myObj.funcB = function() {
    //Do some more work on this.answer
    return this;
}
myObj.funcC = function() {
    //Use the value of this.answer now that funcA and funcB have made their modifications
    return this;
}
myObj.funcA().funcB().funcC();

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

Для моей конкретной задачи порядок выполнения funcA, funcB и т.д. НЕ имеет значения. Поэтому в моих решениях выше, я технически делаю больше работы, чем требуется, потому что я размещаю все функции в серийном заказе. Все, что для меня важно, это то, что funcC (некоторая функция для отправки результата или отключение запроса) вызывается только после того, как funcA и funcB имеют ВСЕ завершение выполнения. В идеале funcC может каким-то образом прослушать все вызовы промежуточных функций для завершения и THEN будет выполняться? Я надеюсь узнать общий шаблон Javascript для решения этой проблемы.

Спасибо за вашу помощь.

Другая идея: Может быть, передать общий объект funcA и funcB, и когда они завершат выполнение, отметьте общий объект, например sharedThing.funcA = "complete" или sharedThing.funcB = "complete", а затем каким-то образом? выполняются funcC, когда общий объект достигает состояния, в котором все поля отмечены как завершенные. Я не уверен, как именно вы можете заставить funcC ждать этого.

Изменить: Я должен отметить, что я использую Javascript на стороне сервера (Node.js), и я хотел бы изучить шаблон для его решения, используя простой старый Javascript (без использования jQuery или других библиотек). Наверняка эта проблема достаточно общая, что есть чистое чисто-Javascript решение?

4b9b3361

Ответ 1

Если вы хотите сохранить его простым, вы можете использовать систему обратных вызовов на основе счетчика. Вот проект системы, который позволяет синтаксис when(A, B).then(C). (when/then на самом деле просто сахар, но опять же вся система, возможно, есть.)

var when = function() {
  var args = arguments;  // the functions to execute first
  return {
    then: function(done) {
      var counter = 0;
      for(var i = 0; i < args.length; i++) {
        // call each function with a function to call on done
        args[i](function() {
          counter++;
          if(counter === args.length) {  // all functions have notified they're done
            done();
          }
        });
      }
    }
  };
};

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

when(
  function(done) {
    // do things
    done();
  },
  function(done) {
    // do things
    setTimeout(done, 1000);
  },
  ...
).then(function() {
  // all are done
});

Ответ 2

Если вы не используете какие-либо асинхронные функции, а ваш script не нарушает порядок выполнения, то самым простым решением является, как заявлено Pointy и другими:

funcA(); 
funcB();
funcC();

Однако, поскольку вы используете node.js, я считаю, что вы собираетесь использовать асинхронные функции и хотите выполнить funcC после завершения запроса асинхронного ввода-вывода, поэтому вам нужно использовать какой-то подсчет механизмы, например:

var call_after_completion = function(callback){
    this._callback = callback;
    this._args = [].slice.call(arguments,1);
    this._queue = {};
    this._count = 0;
    this._run = false;
}

call_after_completion.prototype.add_condition = function(str){
    if(this._queue[str] !== undefined)
        throw new TypeError("Identifier '"+str+"' used twice");
    else if(typeof str !== "String" && str.toString === undefined)
        throw new TypeError("Identifier has to be a string or needs a toString method");

    this._queue[str] = 1;
    this._count++;
    return str;
}

call_after_completion.prototype.remove_condition = function(str){
    if(this._queue[str] === undefined){
        console.log("Removal of condition '"+str+"' has no effect");
        return;
    }
    else if(typeof str !== "String" && str.toString === undefined)
        throw new TypeError("Identifier has to be a string or needs a toString method");

    delete this._queue[str];

    if(--this._count === 0 && this._run === false){
        this._run = true;
        this._callback.apply(null,this._args);
    }
}

Вы можете упростить этот объект, проигнорировав идентификатор str и просто увеличивая/уменьшая this._count, однако эта система может быть полезна для отладки.

Чтобы использовать call_after_completion, вы просто создаете new call_after_completion с нужной функцией func в качестве аргумента и add_condition s. func будет вызываться только при удалении всех условий.

Пример:

var foo = function(){console.log("foo");}
var bar = new call_after_completion(foo);
var i;

bar.add_condition("foo:3-Second-Timer");
bar.add_condition("foo:additional function");
bar.add_condition("foo:for-loop-finished");

function additional_stuff(cond){
    console.log("additional things");
    cond.remove_condition("foo:additional function");
}

for(i = 0; i < 1000; ++i){

}
console.log("for loop finished");
bar.remove_condition("foo:for-loop-finished");
additional_stuff(bar);

setTimeout(function(){
    console.log("3 second timeout");
    bar.remove_condition("foo:3-Second-Timer");
},3000);

JSFiddle Demo

Ответ 3

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

Если вы хотите закончить что-то, что выглядит так, как это было бы в синхронном случае, попробуйте использовать концепцию концепции отсроченного/обещания (это все еще простой JavaScript), например. используя пакет deferred, вы можете найти что-то простое:

// Invoke one after another:
funcA()(funcB)(funcC);

// Invoke funcA and funcB simultaneously and afterwards funcC:
funcA()(funcB())(funcC);

// If want result of both funcA and funcB to be passed to funcC:
deferred(funcA(), funcB())(funcC);

Ответ 4

Посмотрите на отложенные объекты jQuery. Это обеспечивает сложное средство управления тем, что происходит в асинхронной среде.

Очевидным вариантом использования является AJAX, но это не ограничивается этим.

Ресурсы

Ответ 5

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

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

function getCallbackCreator( number_of_data_callbacks, final_callback ) {

    var all_data = {}

    return function ( data_key ) {

        return function( data_value ) {
            all_data[data_key] = data_value;

            if ( Object.keys(all_data).length == number_of_data_callbacks ) {
                final_callback( all_data );
            }
        }
    }
}

var getCallback = getCallbackCreator( 2, inflatePage );

myGoogleDataFetcher( getCallback( 'google' ) );
myCartoDataFetcher( getCallback( 'cartodb' ) );

Изменить: вопрос был помечен node.js, но OP сказал: "Я ищу общий шаблон программирования Javascript для этого", поэтому я разместил его, хотя я не использую node.

Ответ 6

how about:
funcC(funcB(funcA)));

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