简体   繁体   中英

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

I'm working on [a fork of] jWizard , adding support to allow event handlers to return Promise objects .

The following code works as expected:

  1. Invoke the element's "stephide" event.
  2. If the event isn't canceled, hide the element.
  3. When the element is hidden, invoke its stephidden event.

(simplified for this question; see original source )

/** 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();
}

However, when I modify the code to expect a Promise , the callback passed to $el.hide() doesn't fire. The element does get hidden, but the callback doesn't get called.

_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();
});

What am I doing wrong?

I see the following possible issues:

  1. If hide.returnValue is a promise and it isn't resolved when you think it is, then that could cause a problem (we can't see that code).

  2. The callback to .hide() will not get called if something interrupts the hide animation (eg a .stop() before the animation completes). You can get around the interrupted issue by using .promise() instead of the callback because that is always resolved even if the animation is stopped.

  3. If the object is removed from the DOM before the animation is completed (using something like jQuery .remove() or .empty() or .html() of a parent, then it will not call it's callback.

It's also generally a simpler interface to always return a promise (in your hide.returnValue ) if you're ever going to return a promise. If there's nothing to wait for then just return a promise that is already resolved. That makes the calling code a lot simpler because they can just assume there's a promise there and write one set of code. You make it sound like that is only sometimes a promise which is a pain to code for.

I'd suggest a simplification of your promise code where you can use existing promises.


I think you can do everything you wanted with this simplified code:

_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");
        });
    });
}

If hide.returnValue is always a promise, then you don't need the $.when() as you can just call .then() on the promise directly. Using $.when() around it makes the .then() also execute even if hide.returnValue is not a promise.

This also uses the built in .promise() function for the hide animation rather than the callback for the increased reliability of that and the consistency of using promises.


If event.returnValue is always a promise object, then you can use this shortened version:

_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");
        });
    });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM