如何在自定义事件中使用jQuery Deferred?

23
我有两个抽象进程(例如,在js对象中使用揭示模式进行管理,不暴露其内部),它们在完成时会触发自定义事件。当这两个自定义事件都触发时,我想执行一个操作。
jQuery 1.5中的新Deferred逻辑似乎是管理此过程的理想方式,但是when()方法需要返回一个promise()的Deferred对象(或普通js对象,但是then()将立即完成而没有等待的效果,对我来说是无用的)。
理想情况下,我想做这样的事情:
//execute when both customevent1 and customevent2 have been fired
$.when('customevent1 customevent2').done(function(){
  //do something
});

什么是将这两种技术结合在一起的最佳方式?
2个回答

40

http://jsfiddle.net/ch47n/

我创建了一个小插件,它创建了一个新的jQuery.fn.when方法。

语法为:

jQuery( "whatever" ).when( "event1 event2..." ).done( callback );

它在内部广泛使用jQuery.when() 并确保在解决之前所有元素集合中的所有事件都已被触发。


以下是实际的插件代码:

( function( $ ) {

    $.fn.when = function( events ) {

        var deferred, $element, elemIndex, eventIndex;

        // Get the list of events
        events = events.split( /\s+/g );

        // We will store one deferred per event and per element
        var deferreds = [];

        // For each element
        for( elemIndex = 0; elemIndex < this.length; elemIndex++ ) {
            $element = $( this[ elemIndex ] );
            // For each event
            for ( eventIndex = 0; eventIndex < events.length; eventIndex++ ) {
                // Store a Deferred...
                deferreds.push(( deferred = $.Deferred() ));
                // ... that is resolved when the event is fired on this element
                $element.one( events[ eventIndex ], deferred.resolve );
            }
        }

        // Return a promise resolved once all events fired on all elements
        return $.when.apply( null, deferreds );
    };

} )( jQuery );

1
优秀的解决方案。如果节点或事件很多,使用“each”进行多个内部循环可能会导致一些性能问题,但是我目前真的看不到可以改进这个解决方案的方法。感谢您的答案。 - Adam Flynn
2
http://jsfiddle.net/FfPXq/6/ 使用for循环,并使用$.Deferred(fn)签名创建正确的闭包来解除绑定。 - Julian Aubourg
+1 太棒了!我将使用类似的方法异步加载自定义UI控件。在延迟对象出现之前,管理多层事件是非常麻烦的! - Brian

13

当"customevent1"和"customevent2"触发时,您可以使它们各自的事件处理程序发出一个"Deferred"实例的信号。然后,您可以使用"$ .when()"将这两个事件组合成一个事件,并在此处放置处理程序,只有在两个自定义事件都触发后才会触发。

var df1 = $.Deferred(), df2 = $.Deferred();
$('whatever').bind('customevent1', function() {
  // code code code
  df1.resolve();
}).bind('customevent2', function() {
  // code code code
  df2.resolve();
});

var whenBoth = $.when(df1, df2);

whenBoth.then(function() {
  // code to run after both "customevent1"
  // and "customevent2" have fired
});

旧答案,此处仅为完整性

您可以创建自己的Deferred对象来跟踪这两个条件,并在两个条件都满足时触发“resolve”:

function watchEvents() {
  var df = $.Deferred();

  var flags = {};
  $.each(Array.prototype.slice.call(arguments, 0), function() {
    flags[this] = false;
  });

  var realResolve = df.resolve.bind(df);
  df.resolve = function(eventName) {
    flags[eventName] = true;
    for (var ev in flags) if (flags[ev] === false) return;
    realResolve();
  };

  return df;
}

现在你可以调用该函数:

var df = watchEvents("customevent1", "customevent2");

现在,对于那些事件,你的事件处理程序只需要在捕获到事件时调用那个东西上的 "resolve" 方法:

    df.resolve(event.type);

每个处理程序都报告自己的类型。只有当您在调用“watchEvents”时请求的所有事件类型都发生时,您在“df”上注册的处理程序函数才会被调用。
我想到另一件事情,那就是编写一个jQuery插件,为元素初始化一个Deferred对象,并将其存储在“.data()”属性中。然后,您可以编写更多的插件,供事件处理程序使用来发出信号,以及其他插件来注册多事件序列的处理程序。我认为这非常酷,但我需要花些时间深思熟虑。

上面的“df.reserve”是打错了吗?我在jQuery Deferred API文档中找不到reserve属性。如果存在的话,您能提供一个链接吗?谢谢。 - Adam Flynn
还有一种更好的方法来完成这个任务,但我正在权衡几种不同的方式,很快就会更新。 - Pointy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接