Reputation: 3672
With jQuery deferreds I'm used to be able to check the current state like this:
var defer = $.Deferred();
defer.state(); //Returns the state of the deferred, eg 'resolved'
Is there a way to do the same for Angular deferreds? (or even better promises)
Upvotes: 41
Views: 44058
Reputation: 2275
I whipped up a solution inspired by Gil and Travis's answers, which decorates the Promise constructor with methods closer to the Q implementation.
Note that this decoration relies on Promise.$$state
. This was built for Angular 1.6.4, and theoretically should work all the way down to 1.3.x, but no guarantees on that or future releases:
(function() {
'use strict';
angular
.module('your.module.name.goes.here')
.config(configBlock);
/** @ngInject */
configBlock.$inject = ['$provide'];
function configBlock($provide) {
$provide.decorator('$q', ['$delegate', function ($delegate) {
console.log($delegate);
var Promise = $delegate.prototype.constructor;
Promise.prototype.inspect = function () {
var inspect = {};
switch (this.$$state.status) {
case -1:
case 0:
inspect.state = 'pending';
break;
case 1:
inspect.state = 'fulfilled';
break;
case 2:
inspect.state = 'rejected';
break;
default:
inpsect.state = 'unknown';
}
return inspect;
};
Promise.prototype.isFulfilled = function () {
return this.inspect().state === 'fulfilled';
}
Promise.isFulfilled = function (obj) {
if (obj.constructor !== Promise) {
return true;
}
return obj.isFulfilled();
}
Promise.prototype.isRejected = function () {
return this.inspect().state === 'rejected';
}
Promise.isRejected = function (obj) {
if (obj.constructor !== Promise) {
return false;
}
return obj.isRejected();
}
Promise.prototype.isPending = function () {
return this.inspect().state === 'pending';
}
Promise.isPending = function (obj) {
if (obj.constructor !== Promise) {
return false;
}
return obj.isPending();
}
return $delegate;
}]);
}
})();
Upvotes: 0
Reputation: 276396
Update:
Due to refactoring of $q this is now possible although not documented:
promise.$$state.status === 0 // pending
promise.$$state.status === 1 // resolved
promise.$$state.status === 2 // rejected
Original:
Unlike most promise libraries (Bluebird,Q, when, RSVP etc), $q does not expose a synchronous inspection API.
There is no way to achieve this from the outside.
You have to call .then
on the promise and code in that handler will run when the promise fulfills.
Upvotes: 86
Reputation: 35920
The answer to your question is: yes, there is a way. The other answers nicely cover the built-in limitations of $q
. However, it's easy to add a state property to $q
using the $provide
service's decorator function.
$provide.decorator('$q', function ($delegate) {
var defer = $delegate.defer;
$delegate.defer = function() {
var deferred = defer();
deferred.promise.state = deferred.state = 'pending';
deferred.promise.then(function() {
deferred.promise.state = deferred.state = 'fulfilled';
}, function () {
deferred.promise.state = deferred.state = 'rejected';
});
return deferred;
};
return $delegate;
});
Put this decorator inside of a config
block, and all $q
-instantiated deferred and promise objects will have a state
property with the value pending, fulfilled, or rejected.
you are effectively modifying $q itself, wrapping every deferred with another deferred
Actually this is not the case. $q
's original defer()
constructor is called exactly one time. It is simply decorated with additional functionality by internally attaching an event handler via then
. [Note that an additional defer
object is instantiated as a result of the additional then
callback which is automatically created with each deferred object... which is to be expected because this is how angular works internally.]
this wouldn't work because promises shouldn't be created with deferred but chained from promises that are returned from apis
Note that this code will decorate every deferred (and thus promise
object) which is created by the $q
service. This means that any API which utilizes $q will be automatically decorated with the state
property. So regardless of how you use $q
, whether with some API or on it's own, this solution decorates both the deferred
object and the promise
, and I have provided the plunk to prove it.
This approach is unit testable, it's guaranteed not to break any application already using $q
, and it's flexible in the sense that you could later add additional decorators to $q
without modifying the old one(s).
Upvotes: 34
Reputation: 636
Unfortunately this doesn't looks like its possible with $q
. You'll have to put this code inside your then
method.
myPromise()
.then(function() {
// everything in here resolved
},
function() {
// everything in here rejected
},
function() {
// everything in here pending (with progress back)
});
This is for the Q library not angular's $q
but similar.
Angular is inspired by the Q
library, check out the source, its actually not that scary. https://github.com/kriskowal/q/blob/v1/q.js
You can use myPromise.inspect().state
there are ['pending', 'rejected', 'fulfilled']
You also have:
myPromise.isFulfilled();
myPromise.isPending();
myPromise.isRejected();
Check out this JSfiddle and open the console for logged results. http://jsfiddle.net/S6LzP/
More granular, Looking at the defer
function on line 488:
function defer() {
// if "messages" is an "Array", that indicates that the promise has not yet
// been resolved. If it is "undefined", it has been resolved. Each
// element of the messages array is itself an array of complete arguments to
// forward to the resolved promise. We coerce the resolution value to a
// promise using the `resolve` function because it handles both fully
// non-thenable values and other thenables gracefully.
var messages = [], progressListeners = [], resolvedPromise;
var deferred = object_create(defer.prototype);
var promise = object_create(Promise.prototype);
promise.promiseDispatch = function (resolve, op, operands) {
var args = array_slice(arguments);
if (messages) {
messages.push(args);
if (op === "when" && operands[1]) { // progress operand
progressListeners.push(operands[1]);
}
} else {
nextTick(function () {
resolvedPromise.promiseDispatch.apply(resolvedPromise, args);
});
}
};
// XXX deprecated
promise.valueOf = function () {
if (messages) {
return promise;
}
var nearerValue = nearer(resolvedPromise);
if (isPromise(nearerValue)) {
resolvedPromise = nearerValue; // shorten chain
}
return nearerValue;
};
promise.inspect = function () {
if (!resolvedPromise) {
return { state: "pending" };
}
return resolvedPromise.inspect();
};
if (Q.longStackSupport && hasStacks) {
try {
throw new Error();
} catch (e) {
// NOTE: don't try to use `Error.captureStackTrace` or transfer the
// accessor around; that causes memory leaks as per GH-111. Just
// reify the stack trace as a string ASAP.
//
// At the same time, cut off the first line; it's always just
// "[object Promise]\n", as per the `toString`.
promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1);
}
}
// NOTE: we do the checks for `resolvedPromise` in each method, instead of
// consolidating them into `become`, since otherwise we'd create new
// promises with the lines `become(whatever(value))`. See e.g. GH-252.
function become(newPromise) {
resolvedPromise = newPromise;
promise.source = newPromise;
array_reduce(messages, function (undefined, message) {
nextTick(function () {
newPromise.promiseDispatch.apply(newPromise, message);
});
}, void 0);
messages = void 0;
progressListeners = void 0;
}
deferred.promise = promise;
deferred.resolve = function (value) {
if (resolvedPromise) {
return;
}
become(Q(value));
};
deferred.fulfill = function (value) {
if (resolvedPromise) {
return;
}
become(fulfill(value));
};
deferred.reject = function (reason) {
if (resolvedPromise) {
return;
}
become(reject(reason));
};
deferred.notify = function (progress) {
if (resolvedPromise) {
return;
}
array_reduce(progressListeners, function (undefined, progressListener) {
nextTick(function () {
progressListener(progress);
});
}, void 0);
};
return deferred;
}
Mostly notably the method at the very bottom deferred.notify
.
Example usage:
function requestOkText(url) {
var request = new XMLHttpRequest();
var deferred = Q.defer();
request.open("GET", url, true);
request.onload = onload;
request.onerror = onerror;
request.onprogress = onprogress;
request.send();
function onload() {
if (request.status === 200) {
deferred.resolve(request.responseText);
} else {
deferred.reject(new Error("Status code was " + request.status));
}
}
function onerror() {
deferred.reject(new Error("Can't XHR " + JSON.stringify(url)));
}
function onprogress(event) {
deferred.notify(event.loaded / event.total);
}
return deferred.promise;
}
requestOkText("http://localhost:3000")
.then(function (responseText) {
// If the HTTP response returns 200 OK, log the response text.
console.log(responseText);
}, function (error) {
// If there's an error or a non-200 status code, log the error.
console.error(error);
}, function (progress) {
// Log the progress as it comes in.
console.log("Request progress: " + Math.round(progress * 100) + "%");
});
Upvotes: 1