为什么我们更喜欢在Angular中使用$q而不是$http?

14

我目前正在使用Angular的$q服务来进行API调用,代码如下:

var deferred = $q.defer();
$http.get(config.apiHost + details.url)
    .success(function (data) {
        deferred.resolve(data);
    }).error(function (msg) {
        deferred.reject(msg);
    });
return deferred.promise;

但我们也可以使用这种方法,而不必使用$q:

return $http.get(config.apiHost + details.url)
    .success(function (data) {
        return data;
    }).error(function (msg) {
        return msg;
    });

由于$http本身就返回了Promise,因此我还可以使用更简化的方法:

$http.get(config.apiHost + 'posts')
        .success(function (data) {
            console.log(data)
        }).error(function (msg) {
            console.log(msg);
        });

那么$q和$http之间有什么区别呢?它们都返回promise并且都是异步的。Angular是否提供了一些额外的功能来处理$q?我无法找到任何好的答案。


你可以使用$q将Promise与其他异步操作一起返回。$http从AJAX调用中返回Promise。 - Hoyen
其他操作,例如? - Bhushan Goel
像读取文件或者如果您需要使用$timeout。 - Hoyen
$q 主要用于与默认不支持 Promise 的库兼容,并且 当您不能依赖原生的 Promise 实现时。否则,您没有理由使用它。例如,如果您想创建一个基于 Promise 的 $timeout,那么可以使用它。 - Dan
好的,这意味着对于 API 调用,我们可以简单地使用 $http 而不是 $q,因为它们都返回 Promise。只有当某些库不支持原生 Promise 时,$q 才有用。 - Bhushan Goel
@BhushanGoel 是的,我想那应该是最简单的解释方式了。 - Hoyen
3个回答

9

$http 使用 $q,第一个例子是冗余的,第二个也是。只需要返回 $http.get 返回的 promise:

return $http.get(config.apiHost + details.url);

上面的内容与你的第一段代码相同。

另外,return msg并不等同于deferred.reject(msg)。相当于的写法应该是throw msgreturn $q.reject(msg)

还要注意的一点是successerror是非标准的用法,应该使用thencatch


我不会说successerror是“非标准”的,只是已被弃用:https://docs.angularjs.org/api/ng/service/$http - pulse0ne
1
@pulse0ne 如果你在谈论A+ Promise规范,那么它们确实是非标准的。 - Dan
@DanPantry 哦,是的,我误以为你指的是angular $http服务。关于规范,它是非标准的,几乎可以肯定这就是angular开发人员弃用它的原因。 - pulse0ne

4

$q 主要被用于与默认不支持 Promise 的库兼容、并且当你不能依赖本地实现的 Promise(例如 - 在像 IE9 这样的旧版本浏览器中)时。否则没有理由(使用它)。一个例子是,如果你想要创建基于 Promise 的 $timeout$http 本身出于这些确切的原因在后台使用了 $q

与其他已删除的答案所建议的不同,您不需要使用 $q 来“存储”$http promise 的结果。我不建议存储 promise(因为这往往会导致意大利面式的代码),但如果你一定要这样做,你可以直接存储从 $http 返回的 promise;promise 只会执行一次。

在一个 promise 解决/拒绝之后传递给 then 的任何函数都将在下一个 tick 上解决,而不会重新调用最初创建 promise 的原始操作 - 换句话说,promise 的结果被记忆在该对象中。

请注意,Promise 链是有序的,这超出了本回答的范围,但它基本上意味着以下代码是等效的。

function legacyGet() {
  const deferred = $q.defer()
  $http.get('http://www.google.com')
    .then((response) => deferred.resolve(Object.assign(response, {foo: bar}))
    .catch((error) => deferred.reject(error))
  return deferred.defer
}

function modernGet() {
  return $http.get('http://www.google.com')
    .then((response) => Object.assign(response, {foo: bar}))
}

总结一下:您的标题是错误的。我们不喜欢使用 $q,只在必要时才会使用它。建议使用 ES6 Promise,除非您需要支持没有 ES6 Promise 的浏览器 并且 不能使用 polyfill。


0

在Angular中,大多数服务都只返回承诺,但有些情况下,您可能希望使用$q创建自己的延迟对象。

情况1

当您使用不支持承诺的库或创建自己的函数并希望返回承诺时。

情况2

当您使用任何构造时,默认返回承诺,但您希望根据某些条件返回单独的承诺。

例如:在Angular中,$http仅返回承诺,但现在如果您希望如果此承诺的响应包含特定值,则仅返回已解决的内容,否则返回失败,则需要创建自己的延迟对象并根据$http响应返回的值来解决或失败它。


你不需要这样做。使用Promise链即可。你可以像这样 $http.get().then(...)then返回的值将传递给链中的下一个.then。无需为此使用自己的deferred对象。 - Dan
我在谈论$http的响应,比如说如果你想要当响应包含特定值时才解决promise或者拒绝它。 - Rishi Tiwari
1
这个答案不准确,并描述了常见的延迟反模式。 - charlietfl
如果你想根据响应的值拒绝一个 Promise,那么是的,我同意 - 这是一个需要使用 $q 的情况(但你仍然可以返回 $q.reject() 而不是使用 deferred antipattern)... 但这似乎是一个非常狭窄的用例。 - Dan
但这只是一个情况,否则Angular服务已经返回承诺了...为什么要使用延迟对象呢? - Rishi Tiwari
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接