Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more

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 value may be of any type and have any properties, so it's not safe to just just for the presence of the promise methods.

Here's an example of how I'd like my function to behave:

function displayMessage(message) {
  if (message is a Promise or Deferred) {
    message.then(displayMessage);
  } else {
    alert(message);

Notice the recursive handling of promises: if a promise is resolved with another promise value we don't display it, we wait for it to be resolved. If it returns yet another promise, repeat.

This is important because if this were not the case, I would just be able to use jQuery.when:

function displayMessage(message) {
  jQuery.when(message).then(function(messageString) {
    alert(messageString);

This would handle values and promises of values correctly...

displayMessage("hello");                            // alerts "hello"
displayMessage(jQuery.Deferred().resolve("hello")); // alerts "hello"

...but once we get to promises of promises of values, it breaks down:

displayMessage(jQuery.Deferred().resolve(
  jQuery.Deferred().resolve("hello")
));                                                 // alerts "[object Object]"
                Take a look at the instanceof operator: developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/…
– Heretic Monkey
                Oct 25 '12 at 19:15
                @MikeMcCaughan Unfortunately there does not seem to be an exposed Promise type with which to use it.
– Jeremy
                Oct 25 '12 at 19:18
                Is displayMessage meant to be used on types other than strings/deferreds/promise?
– zzzzBov
                Oct 25 '12 at 19:19
                @zzzzBov Yes. This is a simplified example, and I realize it doesn't address that, but in my real code this is a general-purpose function that may handle data of any type.
– Jeremy
                Oct 25 '12 at 19:20
                @Swamp, then why not use two separate methods? One for deferred data-dumping, and another to do the data-dumping?
– zzzzBov
                Oct 25 '12 at 19:21

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:

[...]

  • If Type(resolution) is not Object, then
  • Return FulfillPromise(promise, resolution).
  • Let then be Get(resolution, "then").
  • If then is an abrupt completion, then
  • Return RejectPromise(promise, then.[[value]]).
  • Let thenAction be then.[[value]].
  • If IsCallable(thenAction) is false, then
  • Return FulfillPromise(promise, resolution).
  • Perform EnqueueJob ("PromiseJobs", PromiseResolveThenableJob, «‍promise, resolution, thenAction»)
  • I would upvote this, because I was just going to mention how jQuery does it behind the scenes, but I can't approve that solution because Promises can and should be creatable by other libraries, such as Q or When (or even WinJS!), and your solution won't work with them. In my opinion, the jQuery solution is ok. Maybe, we could just check for the presence of more functions besides .promise (such as .then), that's enough I think. – Camilo Martin Nov 24 '12 at 3:45

    The quick-and-dirty solution is to test if the object has a then function:

    if (typeof message.then === 'function') {
        //assume it's a Deferred or fits the Deferred interface
    } else {
        //do other stuff
                    angularJS promises utilize then() method so this helped me. jquery's when() did not work in my case.
    – Adil F
                    Dec 11 '13 at 23:06
                    This is a "thenable" concept clearly defined in Promises/A+ specification: promises-aplus.github.io/promises-spec/#point-7
    – noitseuq
                    Jul 8 '14 at 13:48
                    @Jeremy Banks, did you mean to significantly revise my answer? If so, it's common courtesy to at least leave a comment before doing so.
    – zzzzBov
                    Apr 19 '16 at 17:42
                    I disagree, and do this often. Given the ease with which edits can be rolled-back, I think it's much more productive for users to feel empowered to make changes, to "edit boldly" in Wikipedia parlance, rather than worrying about stepping on an "owner"'s toes when we should be treating this as a community resource. Rather, I felt it was a courtesy to put this in your answer, because it was a clarification of the approach you had described, rather than trying to take the rep for myself. If you don't like the change, well, it only lived for eight minutes.
    – Jeremy
                    Apr 19 '16 at 17:58
                    @JeremyBanks, to confirm, you actively disagree that it's a common courtesy to notify an author of significant changes you're making to their post which will affect their reputation, possibly negatively? I didn't say you shouldn't be making changes, I'm simply requesting that you reevaluate how you make them.
    – zzzzBov
                    Apr 19 '16 at 18:14
            

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.