在回调中使用 $q - 这可能吗?

Using $q within a callback - is this possible?

我用下面的代码创建了一个简单的例子。目标是能够使用来自 Angular 的 $q 服务的 deferred 承诺,但在回调中有 $q 本身 return 的结果是在主控制器中处理。

我认识到错误显然是 $q 的承诺需要立即 returned 以便它可以 "await" 结果并将该承诺放在回调中禁止它立即被 returned。因此,下面的代码显然是一个错误的策略。

我的问题是要问最佳实践是什么来实现与上述愿望相当的效用,包括存在回调和需要 return 承诺。

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);
});

好吧,我真的想通了。您创建 deferred 然后将其传递给回调。在我发布这篇文章之前对我来说应该是显而易见的,但也许它会帮助其他像我一样感到困惑的人:

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);
});

为了整合评论并解决其他答案对异步 API 的描述有些混乱,我决定提供一个答案:

如果我们假设有一些异步非承诺/基于回调的API,例如,asyncGreet,它可以像这样被模拟:

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

(就此示例而言,asyncGreet 是第 3 方 API 不在我们的控制范围内

要将其转换为基于 $q 承诺的 API,您将使用 $q(使用 $q.defer 或使用 $q 构造函数 -事实上,我只是注意到 Angular's documentation shows the $q-constructor approach).

因此,例如,您可以创建一个 greeterSvc 服务:

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
          }       
        });

      });
    }
  }
})

greeterSvc.greet API 的消费者然后可以 .then 它,例如,记录一些东西 - 就像你做的那样(虽然我会使用 .catch )

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