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]"
jQuery.when
is able to tell if a value is promise, so apparently it is possible. How can I check?
A deferred object is an object that can create a promise and change its state to resolved or rejected . Deferreds are typically used if you write your own function and want to provide a promise to the calling code. You are the producer of the value. A promise is, as the name says, a promise about future value.
The Deferred object, introduced in jQuery 1.5, is a chainable utility object created by calling the jQuery. Deferred() method. It can register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function.
A promise represents a value that is not yet known. This can better be understood as a proxy for a value not necessarily known when the promise is created. A deferred represents work that is not yet finished. A deferred (which generally extends Promise) can resolve itself, while a promise might not be able to do so.
The deferred. promise() method allows an asynchronous function to prevent other code from interfering with the progress or status of its internal request.
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 Promise
1.
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»)
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 }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With