如何判断一个对象是否为 Angular $q promise?

6

我在项目中有一个非Angular的API库。它有一个.request方法,返回jQuery.Deferred promises。我创建了一个简单的Angular服务来包装.request方法,将其结果转换为Angular $q promise。它大致是这样的:

var module = angular.module('example.api', []);

module.factory('api', function(
    $q,
    $window
) {
    function wrappedRequest() {
        var result = $window.API.request.apply($window.API, arguments);
        return $q.when(result);
    };

    return {
        request: wrappedRequest
    };
});

我想写一个测试以确保此服务正常运行。它将提供一个带有API的模拟$window,该API的request方法返回jQuery.Deferred promises。我需要确保生成的对象是Angular $q promise。
如何确定一个对象是否是Angular $q promise?
对于这个问题中给出的示例,区分jQuery.Deferred promises和Angular $q promises已足够,但理想情况下,我们应该能够普遍地识别Angular $q promises。
3个回答

9
通常更好的方法是将您拥有的任何对象转换为Angular Promise。
吸收 thenable 的概念是 Promises / A + 规范的一部分。大多数 Promise 库都有一种方法来做到这一点。它使得 Promise 实现之间具有出色的互操作性和统一的 API。
为此,$q 使用 `.when`:
包装一个可能是值或(第三方)then-able Promise 的对象到 $q Promise 中。当您处理可能是 Promise 的对象时,或者 Promise 来自不能信任的来源时,这非常有用。
它使用 thenables 的概念将“不可信”的 Promise 转换为 $q Promise。
所以你需要做的就是:
var p = $q.when(value); // p is now always a $q promise
                        // if it already was one - no harm

1
如果你只是想在测试中测试$q.when是否有效(如果你真的这样做,那么你不应该这样做),你可以模拟$q并添加一个方法来进行测试。 - Benjamin Gruenbaum

4
作为部分解决方案,针对给定的示例,我们可以通过检查特定方法的存在来轻松区分jQuery.Deferred promises和Angular promises。例如,Angular的$q promises具有处理错误的catch方法,而jQuery.Deferred的promises具有fail方法。
function promiseIsAngularOrJQuery(promise) {
    if (typeof promise.fail === 'function') {
        return 'jquery';
    } else if (typeof promise.catch === 'function') {
        return 'angular';
    } else {
        throw new Error("this can't be either type of promise!");
    }
}

然而,使用这种方法来区分更多类型的承诺,或者将承诺与非承诺之间区分开来,可能会变得非常混乱。不同实现通常使用许多相同的方法名称。它可能能够正常工作,但我不打算走这条路。
在一个非敌对环境下操作受信任的对象,并且只使用单个版本的Angular,有一种可靠地识别$q Promise的替代方案。然而,有些人可能认为它对于严肃的使用来说过于“hacky”。
如果你使用String()函数将函数转换成字符串,则结果是该函数的源代码。我们只需要将潜在承诺对象上的.then方法与已知的$q Promise对象的.then方法进行比较即可。
function isAngularPromise(value) {
    if (typeof value.then !== 'function') {
        return false;
    }
    var promiseThenSrc = String($q.defer().promise.then);
    var valueThenSrc = String(value.then);
    return promiseThenSrc === valueThenSrc;
}

1
为什么需要字符串转换? - Benjamin Gruenbaum
@BenjaminGruenbaum 啊,好主意。我实际上没有检查是否对不同的承诺使用了相同的 then 实例。如果是这样,那就更好了。(我相信 jQuery 确实 为每个 promise 创建唯一的闭包实例 then,因此您需要比较它们的字符串表示而不是身份。)编辑:看起来 Angular 也为每个 promise 的 then 方法创建唯一(绑定的)函数实例,因此比较身份将无法工作。 - Jeremy

2

我的当前解决方案是使用 instanceof

var AngularPromise = $q.resolve().constructor;

console.log($q.resolve() instanceof AngularPromise);  // true

只有当对象确实是Angular Promise时,才保证返回true。

演示:https://jsfiddle.net/DerekL/cmzp7ovj/


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