4

In angularjs, while testing a service, I want to check if the returned object is an Promise.

Right now I am doing the following -

 obj.testMethod()
        .should.be.instanceOf($q.defer());
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
tusharmath
  • 10,622
  • 12
  • 56
  • 83
  • 1
    possible duplicate of [How do I tell if an object is a Promise?](http://stackoverflow.com/questions/27746304/how-do-i-tell-if-an-object-is-a-promise) – Benjamin Gruenbaum Jan 02 '15 at 18:20

4 Answers4

12

Testing if an object is a promise is simple:

return !!obj.then && typeof obj.then === 'function';

That's it. If an object has the then method, it's a promise.

It looks like angular's $q doesn't have anything to differentiate it from other kind of promises.

Florian Margaine
  • 58,730
  • 15
  • 91
  • 116
  • 3
    This is the more correct approach in my opinion, as $q itself will treat anything with a `.then` as a promise. – Benjamin Gruenbaum Apr 15 '14 at 11:35
  • Why the downvote ? For most purposes it looks like the most usable answer. – Denys Séguret Apr 15 '14 at 12:43
  • Maybe also check if `obj.then.length === 2` after first verifying it's a function. A promise's `.then()` method should have two formal parameters (a resolve handler and a reject handler) and the `.length` property of a function should return the number of formal parameters said function accepts. – idbehold Jan 02 '15 at 19:07
  • @idbehold that's probably not the best idea - consider an Angular $q or Q promise or a jQuery deferred that have 3 parameters (one for progression). – Benjamin Gruenbaum Jan 02 '15 at 19:42
  • @BenjaminGruenbaum okay, `obj.then.length >= 2`. – idbehold Jan 02 '15 at 19:55
  • @Florian Margaine what is mean by "!!" in staring of code ? – HaRsH Feb 02 '16 at 12:01
  • -1 No, this isn't true. Why was discussed in the chat. A more formal version is available here: https://gist.github.com/anonymous/ecf9ae41e58e8d9a9d6de8ecdb3c340f – Linus Oleander Aug 25 '16 at 02:38
  • I like this answer, but how about: `return obj && object.then && typeof obj.then==='function';`? Its null/undefined safe and it doesn't bother to cast obj.then to a Boolean since it will just evaluate as truthy anyways. – FoxMulder900 Dec 09 '16 at 02:12
8

Looking at line 248 in the source for $q (https://github.com/angular/angular.js/blob/master/src/ng/q.js#L248) there isn't really a check you can do thats definite. This would be your best bet

var deferred = method();

if(angular.isObject(deferred) && 
   angular.isObject(deferred.promise) && 
   deferred.promise.then instanceof Function && 
   deferred.promise["catch"] instanceof Function && 
   deferred.promise["finally"] instanceof Function){
   //This is a simple Promise
}

If the promise was actually a function where you could use new Promise() then you would be able to use promise instanceof Promise, however it's an object so it doesn't have any special identifiers, the only things you can test are their properties.

EDIT:

To test for a "HttpPromise" then you can add on checks for error and success which are defined in the $http service (https://github.com/angular/angular.js/blob/master/src/ng/http.js#L726):

var promise = $http(...);

if(angular.isObject(promise) && 
   promise.then instanceof Function && 
   promise["catch"] instanceof Function && 
   promise["finally"] instanceof Function && 
   promise.error instanceof Function && 
   promise.success instanceof Function){
   //This is a HttpPromise
}

EXTRA:

If you notice $http doesn't actually return deferred, it returns the straight promise, if you follow the calls it's actually returns $q.when(...) with a couple of functions added to it. You can see that $q.when doesn't return deferred, rather it returns $q.deferred().promise, so in turn $http(...) would never be $q.deferred()

Also, if you were to run the test you had posted I would expect you to get this error:

TypeError: Expecting a function in instanceof check, but got #<Object>

FabianCook
  • 20,269
  • 16
  • 67
  • 115
  • The above code still doesn't check for `HttpPromise` which is not really $q promise. Right? – tusharmath Apr 15 '14 at 09:58
  • Pretty sure they are the same arent they? It returns $q.when with success and error added to it promise declared: https://github.com/angular/angular.js/blob/master/src/ng/http.js#L707 , promise modified to add success & error: https://github.com/angular/angular.js/blob/master/src/ng/http.js#L726, – FabianCook Apr 15 '14 at 11:04
  • Thanks for pointing to the source code. This is definitely useful. – tusharmath Apr 15 '14 at 11:20
  • The great thing with open source projects is that you can drill right down and see how everything is happening. What framework are you using for testing? Is that a real method or psuedo code? I would assume a new "instance" of `$q.deffered();` would have a bit missing, esp the success and error functions. Meaning they wouldn't "equal" so to say, however they would still both be promises – FabianCook Apr 15 '14 at 11:21
  • Its pseudo code and I am using mocha. So I should basically test if its a promise and then check if it has `success` and `error` methods? – tusharmath Apr 15 '14 at 11:25
  • 1
    shouldn't that be deferred, not deffered? – Ozan Bellik Sep 23 '15 at 03:47
0

You could mock $http and have it return some object you create. Then you can check whether your service returns that object.

Pieter Herroelen
  • 5,977
  • 2
  • 29
  • 37
  • the `testMethod` returns a HttpPromise, with success and error methods. I dont want to loose them. – tusharmath Apr 15 '14 at 09:10
  • If you check that (the fact that the object has a success and an error method), is that not sufficient? – Pieter Herroelen Apr 15 '14 at 09:19
  • For the moment I am doing exactly what you mentioned. But it would be great If I could test via `instanceOf` method. Don't you think so? – tusharmath Apr 15 '14 at 09:27
  • It depends on what you think is the API of your service. Does it return "a promise" or does it return an object that has a then and a success and and error function. And what is the definition of "a promise"? You're not writing code in a typed language, that's why it's hard to test for the type of the returned object... Please have a look at http://stackoverflow.com/questions/3379529/duck-typing-in-javascript – Pieter Herroelen Apr 15 '14 at 09:33
0

I was working with callbacks, I needed a CTA on click event to fire a function and determine what type of callback was fired, here is some simple examples.

let fn = function () { alert('A simple function') };

let cta = fn();

console.log(cta instanceof Promise);

// logs false

New Promise Example

let fn = function () {

    return new Promise(function (accept, reject) {

        accept();

        // reject();

    });

};

let cta = fn();

if(cta instanceof Promise) {

    cta.then(function () {

        alert('I was a promise');

    });

}

Axios Example 1

let fn = function () {

   return axios.get('/user');

}

let cta = fn();

if(cta instanceof Promise) {

    cta.then(function () {

        alert('I was a promise from axios');

    });

}

// Triggers alert('I was a promise from axios');

Axios Example 2

let fn = function () {

   return axios.get('/user').then(response => {
       console.log('user response', response.data);
   });

}

let cta = fn();

if(cta instanceof Promise) {

    cta.finally(function () {

        alert('I was a final promise from axios');

    });

}

// Triggers alert('I was a final promise from axios');
Marc
  • 5,109
  • 2
  • 32
  • 41