如果jQuery.hide()在Deferred回调中被调用,Callback不会触发

Callback doesn't fire if jQuery.hide() is invoked in a Deferred callback

本文关键字:Callback 调用 hide jQuery 回调 Deferred 如果      更新时间:2023-09-26

我正在jWizard的[一个分支]上工作,增加对允许事件处理程序返回Promise对象的支持。

下面的代码按预期工作:

  1. 调用元素的"stephide"事件
  2. 如果事件没有被取消,隐藏元素。
  3. 当元素被隐藏时,调用它的stephidden事件。

(简化为这个问题;(见原文)

/** Invokes the `stephide` event for the current element and hides
 *   it if the event handler doesn't prevent it.
 *
 * @param $el {jQuery} Element that will be hidden.
 */
_leave: function ($el) {
  var event = $.Event("stephide"),
      dfd = $.Deferred(),
      effect = { effect: "blind", direction: "left", duration: 250 };
  $el.trigger(event);
  if(event.isDefaultPrevented()) {
    dfd.reject();
  } else {
    $el.hide(effect, function() {
      $el.trigger("stephidden");
      dfd.resolve();
    });
  }
  return dfd.promise();
}

然而,当我修改代码以期望Promise时,传递给$el.hide()的回调不会触发。元素会被隐藏,但回调函数不会被调用。

_leave: function ($el) {
  var event = $.Event("stephide"),
      dfd = $.Deferred(),
      effect = { effect: "blind", direction: "left", duration: 250 };
  $el.trigger(event);
  // event.returnValue is a Promise object; see below.
  $.when(event.returnValue).then(
    function() {
      // $el.hide DOES run...
      $el.hide(effect, function() {
        // ... but this code never does!
        console.log("stephidden");
        $el.trigger("stephidden");
        dfd.resolve();
      });
    },
    dfd.reject
  );
  return dfd.promise();
}
/** Event handler for the stephide event.
 */
$("...").on("stephide", function(event) {
  var dfd = $.Deferred();
  // ...
  event.returnValue = dfd.promise();
});

我做错了什么?

我发现以下可能的问题:

  1. 如果hide.returnValue是一个承诺,当你认为它是,它没有解决,那么这可能会导致一个问题(我们看不到代码)。

  2. 如果有东西打断了隐藏动画(例如在动画完成之前的.stop()),则不会调用.hide()的回调。你可以通过使用.promise()而不是回调来解决中断问题,因为即使动画停止,这也总是解决的。

  3. 如果对象在动画完成之前从DOM中移除(使用类似于jQuery的.remove().empty().html()的父对象),那么它将不会调用它的回调。

如果你打算返回一个承诺,它通常也是一个更简单的接口,总是返回一个承诺(在你的hide.returnValue中)。如果没有什么需要等待的,那么就返回一个已经解决的promise。这使得调用代码更简单,因为他们可以假设这里有一个承诺,然后编写一组代码。你说得好像那只是有时候的承诺,编码起来很痛苦。

我建议简化你的承诺代码,你可以使用现有的承诺。


我认为你可以用这段简化的代码做任何你想做的事情:

_leave: function ($el) {
  var event = $.Event("stephide"),
      effect = { effect: "blind", direction: "left", duration: 250 };
    $el.trigger(event);
    return $.when(event.returnValue).then(function() {
        return $el.hide(effect).promise().then(function() {
            console.log('stephidden');
            $el.trigger("stephidden");
        });
    });
}

如果hide.returnValue总是一个promise,那么你不需要$.when(),因为你可以直接在promise上调用.then()。在它周围使用$.when(),即使hide.returnValue不是承诺,.then()也会执行。

这也使用内置的.promise()函数来隐藏动画,而不是回调,以提高可靠性和使用承诺的一致性。


如果event.returnValue始终是一个promise对象,那么您可以使用以下缩写版本:

_leave: function ($el) {
  var event = $.Event("stephide"),
      effect = { effect: "blind", direction: "left", duration: 250 };
    $el.trigger(event);
    return event.returnValue.then(function() {
        return $el.hide(effect).promise().then(function() {
            console.log('stephidden');
            $el.trigger("stephidden");
        });
    });
}