How can I tell if an object is a jQuery Promise/Deferred?

后端 未结 2 397
生来不讨喜
生来不讨喜 2021-02-05 03:05

I have a function that takes a single argument. I need to be able to tell if this argument is a jQuery Promise or Deferred object. If not, then the val

2条回答
  •  一向
    一向 (楼主)
    2021-02-05 03:36

    jQuery.when is able to tell if a value is promise, so apparently it is possible.

    This is mistaken. jQuery itself is not able to check if an object is a promise with complete accuracy. If you look at the source of jQuery.when in the jQuery source viewer you can see that all it does is this:

    jQuery.isFunction(firstParam.promise)
    

    If the object you are returning has its own .promise() method, jQuery.when will misbehave:

    var trickyValue = {
      promise: function() { return 3; },
      value: 2
    };
    
    jQuery.when(trickyValue).then(function(obj) {
      alert(obj.value);
    });
    

    This throws TypeError: Object 3 has no method 'then', because jQuery assumes the object is a promise and trusts the value of its .promise() method.

    This is probably impossible to solve properly. The promise object is created as an object literal inside of jQuery.Deferred (view source). It has no prototype, nor any other truly unique properties that could be used to distinguish it.

    However, I can think of a hacky solution that should be reliable as long as only one version of jQuery is in use:

    function isPromise(value) {
      if (typeof value === 'object' && typeof value.then !== "function") {
        return false;
      }
      var promiseThenSrc = String($.Deferred().then);
      var valueThenSrc = String(value.then);
      return promiseThenSrc === valueThenSrc;
    }
    
    isPromise("test");                 // false
    isPromise($.Deferred());           // true
    isPromise($.Deferred().promise()); // true
    

    Converting a function to a string gives you its source code, so here I am comparing then source of the .then method of a new Deferred object to that of the value I'm interested in. Your value is not going to have a .then method with exactly the same source code as a jQuery.Deferred or Promise1.

    1. Unless you're running in a hostile environment, in which case you should probably give up.


    If you aren't specifically interested in jQuery promises, but would like to detect any type of Promise including the built-in ones from ECMAScript 6, you can test if value is an object and has a then method:

    if (typeof value === 'object' && typeof value.then === 'function') {
      // handle a promise
    } else {
      // handle a concrete value
    }
    

    This is the approach by several Promise-handling functions defined in ES6. You can see this described in the specification of the resolve(...) functions, partially quoted below:

    When a promise resolve function F is called with argument resolution, the following steps are taken:

    [...]

    1. If Type(resolution) is not Object, then
      1. Return FulfillPromise(promise, resolution).
    2. Let then be Get(resolution, "then").
    3. If then is an abrupt completion, then
      1. Return RejectPromise(promise, then.[[value]]).
    4. Let thenAction be then.[[value]].
    5. If IsCallable(thenAction) is false, then
      1. Return FulfillPromise(promise, resolution).
    6. Perform EnqueueJob ("PromiseJobs", PromiseResolveThenableJob, «‍promise, resolution, thenAction»)

提交回复
热议问题