【问题标题】:How to execute a Javascript function only after multiple other functions have completed?如何仅在多个其他功能完成后才执行 Javascript 功能?
【发布时间】:2012-07-01 22:38:20
【问题描述】:

我的具体问题是我需要执行(可能)大量的 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 可以以某种方式侦听所有要完成的中间函数调用,然后执行?我希望学习一个通用的 Javascript 模式来解决这样的问题。

感谢您的帮助。

另一个想法: 也许将共享对象传递给 funcA 和 funcB 并在它们完成执行时标记共享对象,例如 sharedThing.funcA = "complete" 或 sharedThing.funcB = "complete" 然后以某种方式?当共享对象达到所有字段都标记为完成的状态时,执行 funcC。我不确定你如何让 funcC 等待这个。

编辑: 我应该注意我正在使用服务器端 Javascript (Node.js),我想学习一种模式来解决它,只需使用普通的旧 Javascript(不使用 jQuery 或其他库)。当然,这个问题足够普遍,以至于有一个干净的纯 JavaScript 解决方案?

【问题讨论】:

  • 按照您希望执行它们的顺序构建对函数的引用数组,然后将该数组传递给另一个函数,该函数仅遍历数组调用函数。确保你想成为最后一个。
  • 或者只是按照你想要的执行顺序编写代码;我不清楚问题到底是什么。顺序命令式编码总是让你承诺程序语句的执行顺序;这种情况有什么特别之处?
  • 这很聪明尖利,我从来没想过要那样做。如果您的功能启动有延迟怎么办?还要等完成吗?
  • 这可能有点矫枉过正,但 jQuery 有一个延迟特性。基本上,它允许您使用类似$.when(A, B).then(C) 的语法。
  • 正如Pointy所说,这里真正的问题是什么?这些函数中的任何一个是异步的吗?如果没有,那么 javascript 只是按照你编写的顺序执行它们。

标签: javascript node.js design-patterns asynchronous


【解决方案1】:

查看 jQuery 的延迟对象。这提供了一种复杂的方法来控制在异步环境中发生的情况。

明显的用例是 AJAX,但不限于此。

资源:

【讨论】:

  • 这听起来很有趣,但您提供的 3 个链接中有 2 个已损坏。
【解决方案2】:

如果您想保持简单,可以使用基于计数器的回调系统。这是一个允许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
});

【讨论】:

    【解决方案3】:

    如果您不使用任何异步函数并且您的脚本没有破坏执行顺序,那么最简单的解决方案是,正如 Pointy 和其他人所说:

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

    但是,由于您使用的是node.js,我相信您将使用异步函数并希望在异步IO请求完成后执行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_conditions。 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

    【讨论】:

      【解决方案4】:

      如果您不想使用任何帮助程序库,而需要自己编写一些帮助程序,则没有简单的单行解决方案。

      如果您想以看起来与同步情况下一样可读的内容结束,请尝试一些延迟/承诺概念实现(它仍然是纯 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);
      

      【讨论】:

        【解决方案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 没有完成执行的情况。

          【讨论】:

          • 在@Aleksander Kogut 中回答更清晰的愿景,但从您的方法来看,它可能类似于:result = funcA();结果 = funcB(结果); funcC(结果);因为我会传递结果链。
          • 刚刚更正了previos代码。对我来说现在完美无缺。我什至使用了更多的嵌套函数。
          【解决方案7】:

          如今,人们可以这样做:

          假设我们同时拥有 funcA、funcB 和 funcC:

          如果希望将 funcA 和 funcB 结果传递给 funcC:

          var promiseA = new Promise((resolve, reject) => {
            resolve(await funcA());
          });
          var promiseB = new Promise((resolve, reject) => {
            resolve(await funcB());
          });
          var promise = Promise.all([ promiseA, promiseB ]).then(results => {
            // results = [result from funcA, result from funcB]
            return funcC(results);
          });
          

          如果一个人想要funcA,然后是funcB,然后是funcC:

          var promise = (
            new Promise(async resolve => resolve( await funcA() ))
          ).then(result_a => funcB(result_a)).then(result_b => funcC(result_b));
          

          最后:

          promise.then(result_c => console.log('done.'));
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-05-31
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多