kuanb
kuanb

Reputation: 1708

Using $q within a callback - is this possible?

I've created a simple example of this with the below code. The goal is to be able to used the deferred promise from Angular's $q service but have $q within a callback that itself returns a result that is to be handled in the main controller.

I recognize the error is clearly that the $q's promise needs to be returned immediately so that it can "await" the result and that placing that promise within a callback inhibits it from being returned immediately. Thus, the code below is clearly an incorrect strategy.

My question is to ask what the best practice would be to achieve a comparable utility to the above described desire, including the presence of both a callback and need to return a promise.

function asyncGreet(name, cb) {
  cb(name)
}

function okToGreet(name) {
  return name.length > 10
}

var promise = asyncGreet('Robin Hood', function(name) {
    var deferred = $q.defer();

  setTimeout(function() {
    deferred.notify('About to greet ' + name + '.');

    if (okToGreet(name)) {
      deferred.resolve('Hello, ' + name + '!');
    } else {
      deferred.reject('Greeting ' + name + ' is not allowed.');
    }
  }, 1000);

  return deferred.promise;
});

promise.then(function(greeting) {
  console.log('Success: ' + greeting);
}, function(reason) {
  console.log('Failed: ' + reason);
});

Upvotes: 2

Views: 236

Answers (2)

kuanb
kuanb

Reputation: 1708

Well I actually figured it out. You create deferred and then pass it into the callback. Should have been obvious to me before I posted this but perhaps it'll help someone else who got confused as I did:

function asyncGreet(name, cb) {
  var deferred = $q.defer();

  setTimeout(function() {
    var foo = null;
    cb(name, deferred)
  }, 1000);

  return deferred.promise;
}

var promise = asyncGreet('Robin Hood', function(name, deferred) { 
  if (name.length > 10) {
    foo = 'Hello, ' + name + '!';
  } else {
    foo = 'Greeting ' + name + ' is not allowed.';
  }
  deferred.resolve(foo);
});

promise.then(function(greeting) {
  console.log('Success: ' + greeting);
}, function(reason) {
  console.log('Failed: ' + reason);
});

Upvotes: 2

New Dev
New Dev

Reputation: 49590

To consolidate the comments and to address the other answer's somewhat confusing description of an async API, I decided to provide an answer:

If we suppose that there is some async non-promise / callback-based API, for example, asyncGreet, it could be mocked like so:

function asyncGreet(name, cb){
  // simulate async
  setTimeout(function(){
    someCondition ? cb({message: "Hello, " + name + "!"}) :
                    cb({error: "Greeting " + name + " is not allowed."});
  }, 2000);
}

(for the purposes of this example, asyncGreet is a 3rd party API not in our control)

To convert this to a $q promise-based API, you would use a $q (either with $q.defer or with $q constructor - in fact, I just noticed that Angular's documentation shows the $q-constructor approach).

So, for example, you could create a greeterSvc service:

app.factory("greeterSvc", function greeterSvcFactory($q){
  return {
    greet: function(name){

      return $q(function(resolve, reject){            
        asyncGreet(name, function cb(data){
          if ('error' in data) {
            reject(data.error);    // extract reason
          } else {
            resolve(data.message); // extract greeting message
          }       
        });

      });
    }
  }
})

The consumer of the greeterSvc.greet API could then .then it and, for example, log something - just like you did (although I would use .catch instead)

greeterSvc.greet("Robin Hood")
          .then(function(greeting) {
             console.log('Success: ' + greeting);
          })
          .catch(function(reason) {
             console.log('Failed: ' + reason);
          });

Upvotes: 2

Related Questions