在JavaScript中异步调用数组中的函数

时间:2014-04-09 09:49:24

标签: javascript arrays events asynchronous

我在JavaScript WebApp中创建了一个简单的观察者模型,以处理更复杂的JS-Object模型上的事件监听器(没有DOM事件)。可以注册事件监听器函数,然后将其存储在数组中。通过从更广泛的模型应用程序中调用成员函数,可以执行事件侦听器。到现在为止还挺好。这是适用的实现:

var ModelObserver = function() {
    this.locationObserverList = [];
}

ModelObserver.prototype.emitEvent = function(eventtype, data) {
    for(var i=0; i < this.locationObserverList.length; i++) {
      var fns = this.locationObserverList[i];
      fns(data);  // function is being called
    }
};

ModelObserver.prototype.registerLocationListener = function( fn) {
    this.locationObserverList.push(fn);
};

如果在一个小样本html网站上与两个听众进行测试,那一切都很好。

现在我想异步调用函数。我试着按如下方式更改相应功能的代码:

ModelObserver.prototype.emitEvent = function(eventtype, data) {
    for(var i=0; i < this.locationObserverList.length; i++) {
      var fns = this.locationObserverList[i];
      setTimeout(function() {fns(data);}, 0);
    }
};

不幸的是我在这里有一个问题:只有第二个监听器被调用,但现在两次。它似乎与fns变量冲突,所以我尝试了这个:

ModelObserver.prototype.emitEvent = function(eventtype, data) {
    var fns = this.locationObserverList;
    for(var i=0; i < this.locationObserverList.length; i++) {
      setTimeout(function() {fns[i](data);}, 0);
    }
};

现在我收到一个错误:“未捕获的TypeError:对象[对象数组]的属性'2'不是函数”。

有没有人知道如何让它以异步方式工作?

3 个答案:

答案 0 :(得分:1)

您提供给setTimeout的匿名函数对其关闭的变量有一个持久引用,而不是它们创建时的副本。

你需要让它接近其他东西。通常,您使用一个为setTimeout构建函数的函数,并将args关闭到构建器:

ModelObserver.prototype.emitEvent = function(eventtype, data) {
    for(var i=0; i < this.locationObserverList.length; i++) {
      var fns = this.locationObserverList[i];
      setTimeout(buildHandler(fns, data), 0);
      // Or combining those two lines:
      //setTimeout(buildHandler(this.locationObserverList[i], data), 0);
    }
};

function buildHandler(func, arg) {
    return function() {
        func(arg);
    };
}

在那里,我们调用buildHandler引用函数和我们希望它接收的参数,buildHandler返回一个函数,当调用它时,将使用该参数调用该函数。我们将返回的函数传递给setTimeout

如果您在ES5环境中(或包含适当的垫片,因为它是可调制的),您也可以使用ES5 Function#bind执行此操作:

ModelObserver.prototype.emitEvent = function(eventtype, data) {
    for(var i=0; i < this.locationObserverList.length; i++) {
      var fns = this.locationObserverList[i];
      setTimeout(fns.bind(undefined, data), 0);
      // Or combining those two lines:
      //setTimeout(this.locationObserverList[i].bind(undefined, data), 0);
    }
};

删除一些细节,这基本上与buildHandler上面的内容有关。

更多相关信息(在我的博客上):Closures are not complicated


旁注:通过安排稍后通过setTimeout调用这些函数,我不认为您可以依赖它们按顺序调用。也就是说,即使您安排了1,2和3,我也不知道您可以依靠这种方式进行调用。 The (newish) spec for this指的是&#34;列表&#34;定时器,建议顺序,因此人们可能会想到以相同的超时以特定顺序注册定时器会使它们按该顺序执行。但我不会(略读)在规范中看到任何保证这一点的东西,所以我不想依赖它。 A very quick and dirty test建议我尝试过的实现就是这样做的,但它不是我依赖的东西。

答案 1 :(得分:0)

ModelObserver.prototype.emitEvent = function(eventtype, data) {
     var fns = this.locationObserverList;
     for(var i=0; i < this.locationObserverList.length; i++) {
         (function(j){
             setTimeout(function() {fns[i](data);}, 0);
         }(i));
    }
};

试试这个

答案 2 :(得分:0)

第二次尝试不起作用。在您的第一个示例中尝试 -

setTimeout(function(){this.fns(data);},0);