获取 Angular 的延迟状态?

Posted

技术标签:

【中文标题】获取 Angular 的延迟状态?【英文标题】:Get state of Angular deferred? 【发布时间】:2014-07-28 07:05:24 【问题描述】:

使用 jQuery deferreds 我习惯于像这样检查当前状态:

var defer = $.Deferred();
defer.state();  //Returns the state of the deferred, eg 'resolved'

有没有办法对 Angular deferreds 做同样的事情? (甚至更好的承诺)

【问题讨论】:

【参考方案1】:

更新

由于 $q 的重构,这现在是可能的,尽管没有记录:

promise.$$state.status === 0 // pending
promise.$$state.status === 1 // resolved
promise.$$state.status === 2 // rejected

原创

与大多数 Promise 库(Bluebird、Q、when、RSVP 等)不同,$q 不公开同步检查 API。

没有办法从外部实现这一点。

您必须在 Promise 上调用 .then,并且该处理程序中的代码将在 Promise 实现时运行。

【讨论】:

how-do-i-use-bluebird-with-angular 它看起来像是被添加到 1.3.0 - github.com/angular/angular.js/blob/v1.3.0/src/ng/q.js。在此之前我找不到任何对 $$state 的引用。 status 也可以是-1,但我不确定它意味着什么? github.com/angular/angular.js/blob/v1.4.3/src/ng/q.js#L365 以“$$”开头的属性应该被视为私有API,而不是在您自己的代码中使用。但是,我认为没有公开的 API 用于公开 Angular 承诺的状态。 @muttonUp: -1 似乎是第二个已解决的情况,如 1。它用于表示已解决的值何时是一个函数,或者该值是否需要沿承诺链传递。我想 -1 的意思是“到目前为止还不错,但在解决方案完成之前我们还有更多代码要运行”。【参考方案2】:

您的问题的答案是:是的,有办法。其他答案很好地涵盖了 $q 的内置限制。但是,使用 $provide 服务的装饰器函数很容易将状态属性添加到 $q

  $provide.decorator('$q', function ($delegate) 
    var defer = $delegate.defer;
    $delegate.defer = function() 
      var deferred = defer();

      deferred.promise.state = deferred.state = 'pending';

      deferred.promise.then(function() 
        deferred.promise.state = deferred.state = 'fulfilled';
      , function () 
        deferred.promise.state = deferred.state = 'rejected';
      ); 

      return deferred;
    ;
    return $delegate;
  );

将此装饰器放在config 块内,所有$q-instantiated deferredpromise 对象都将有一个state 属性值为待处理、已完成已拒绝

查看this plunk


怀疑?

你正在有效地修改 $q 本身,用另一个 deferred 包装每个 deferred

实际上并非如此。 $q 的原始defer() 构造函数只被调用一次。它通过then 在内部附加一个事件处理程序,简单地装饰了附加功能。 [请注意,附加 then 回调的结果会实例化一个附加 defer 对象,该回调是随每个 deferred 对象自动创建的...这是意料之中的,因为这就是 angular 的工作方式内部。]

这行不通,因为不应使用 deferred 创建 Promise,而是将其与从 API 返回的 Promise 链接起来

请注意,此代码将装饰由 $q 服务创建的 每个 延迟(以及 promise 对象)。这意味着任何使用 $q 的 API 都将自动使用 state 属性进行修饰。所以不管你如何使用$q,无论是使用一些API还是它自己,这个解决方案都装饰了deferred对象和promise,我提供了the plunk来证明这一点。


有生产价值吗?

这种方法可单元测试保证不会破坏任何已经使用$q的应用程序,并且灵活您可以稍后在 $q 中添加额外的装饰器,而无需修改旧的。

【讨论】:

是的,这是行不通的,因为不应使用 deferred 创建 Promise,而是将其与从 API 返回的 Promise 链接起来 我已经在回答中回复了你的 cmets。 @NateBarbettini 很好 - 一方面,$q 在 1.2 的生命周期中经历了几次重大变化,在 1.2 和 1.3 之间进行了一次巨大的内部检修,并且几乎经历了更大的检修。今天你将完全不同(延迟和承诺是基于原型的) - 你可以直接覆盖 .then 而无需此解决方案的开销(创建开销并为每个通过 Angular 的承诺分配额外的闭包 i>) Promise 在 Angular 中很慢(最近变得更好)。 我很想知道 Promise 的用例,其中实例化闭包和每个 Promise 的一些简单对象会产生任何可察觉的性能差异。 这在 Angular 1.6.4 中并不一致。这只会修饰通过$q.defer 做出的承诺,并不是每个承诺都是从该方法生成的。具体来说,rejectwhen/resolveall 方法在内部专门调用 new Promise(),而 race 调用 defer。处理这个问题的最好方法是装饰 Promise 的构造函数,但整个 API 是内部的。【参考方案3】:

更新:

不幸的是,这在$q 看来是不可能的。您必须将此代码放入您的 then 方法中。

myPromise()
.then(function() 
    // everything in here resolved
,
function() 
    // everything in here rejected
,
function() 
    // everything in here pending (with progress back)
);

其他:

这是针对 Q 库的,不是 angular 的 $q,而是类似的。

Angular 的灵感来自 Q 库,请查看源代码,它实际上并没有那么可怕。 https://github.com/kriskowal/q/blob/v1/q.js

你可以用myPromise.inspect().state 还有['pending', 'rejected', 'fulfilled']

你还有:

myPromise.isFulfilled();
myPromise.isPending();
myPromise.isRejected();

查看这个 JSfiddle 并打开控制台以查看记录的结果。 http://jsfiddle.net/S6LzP/

更细化,查看第 488 行的 defer 函数:

function defer() 
    // if "messages" is an "Array", that indicates that the promise has not yet
    // been resolved.  If it is "undefined", it has been resolved.  Each
    // element of the messages array is itself an array of complete arguments to
    // forward to the resolved promise.  We coerce the resolution value to a
    // promise using the `resolve` function because it handles both fully
    // non-thenable values and other thenables gracefully.
    var messages = [], progressListeners = [], resolvedPromise;

    var deferred = object_create(defer.prototype);
    var promise = object_create(Promise.prototype);

    promise.promiseDispatch = function (resolve, op, operands) 
        var args = array_slice(arguments);
        if (messages) 
            messages.push(args);
            if (op === "when" && operands[1])  // progress operand
                progressListeners.push(operands[1]);
            
         else 
            nextTick(function () 
                resolvedPromise.promiseDispatch.apply(resolvedPromise, args);
            );
        
    ;

    // XXX deprecated
    promise.valueOf = function () 
        if (messages) 
            return promise;
        
        var nearerValue = nearer(resolvedPromise);
        if (isPromise(nearerValue)) 
            resolvedPromise = nearerValue; // shorten chain
        
        return nearerValue;
    ;

    promise.inspect = function () 
        if (!resolvedPromise) 
            return  state: "pending" ;
        
        return resolvedPromise.inspect();
    ;

    if (Q.longStackSupport && hasStacks) 
        try 
            throw new Error();
         catch (e) 
            // NOTE: don't try to use `Error.captureStackTrace` or transfer the
            // accessor around; that causes memory leaks as per GH-111. Just
            // reify the stack trace as a string ASAP.
            //
            // At the same time, cut off the first line; it's always just
            // "[object Promise]\n", as per the `toString`.
            promise.stack = e.stack.substring(e.stack.indexOf("\n") + 1);
        
    

    // NOTE: we do the checks for `resolvedPromise` in each method, instead of
    // consolidating them into `become`, since otherwise we'd create new
    // promises with the lines `become(whatever(value))`. See e.g. GH-252.

    function become(newPromise) 
        resolvedPromise = newPromise;
        promise.source = newPromise;

        array_reduce(messages, function (undefined, message) 
            nextTick(function () 
                newPromise.promiseDispatch.apply(newPromise, message);
            );
        , void 0);

        messages = void 0;
        progressListeners = void 0;
    

    deferred.promise = promise;
    deferred.resolve = function (value) 
        if (resolvedPromise) 
            return;
        

        become(Q(value));
    ;

    deferred.fulfill = function (value) 
        if (resolvedPromise) 
            return;
        

        become(fulfill(value));
    ;
    deferred.reject = function (reason) 
        if (resolvedPromise) 
            return;
        

        become(reject(reason));
    ;
    deferred.notify = function (progress) 
        if (resolvedPromise) 
            return;
        

        array_reduce(progressListeners, function (undefined, progressListener) 
            nextTick(function () 
                progressListener(progress);
            );
        , void 0);
    ;

    return deferred;

主要是最底部的方法deferred.notify

示例用法:

function requestOkText(url) 
    var request = new XMLHttpRequest();
    var deferred = Q.defer();

    request.open("GET", url, true);
    request.onload = onload;
    request.onerror = onerror;
    request.onprogress = onprogress;
    request.send();

    function onload() 
        if (request.status === 200) 
            deferred.resolve(request.responseText);
         else 
            deferred.reject(new Error("Status code was " + request.status));
        
    

    function onerror() 
        deferred.reject(new Error("Can't XHR " + JSON.stringify(url)));
    

    function onprogress(event) 
        deferred.notify(event.loaded / event.total);
    

    return deferred.promise;


requestOkText("http://localhost:3000")
.then(function (responseText) 
    // If the HTTP response returns 200 OK, log the response text.
    console.log(responseText);
, function (error) 
    // If there's an error or a non-200 status code, log the error.
    console.error(error);
, function (progress) 
    // Log the progress as it comes in.
    console.log("Request progress: " + Math.round(progress * 100) + "%");
);

【讨论】:

我认为 Angular 的 deferred 受到 Q 库的启发,但它们缺少 Q 的很多功能:docs.angularjs.org/api/ng/service/$q...据我所知,这些方法在角度承诺 我的错误,我已经快速浏览了有角度的 github github.com/angular/angular.js/blob/master/src/ng/q.js,看起来这是不可能的,但你应该仍然可以使用 notify。 ://【参考方案4】:

受 Gil 和 Travis 的回答启发,我提出了一个解决方案,它用更接近 Q 实现的方法装饰了 Promise 构造函数。

请注意,此装饰依赖于Promise.$$state。这是为 Angular 1.6.4 构建的,理论上应该可以一直工作到 1.3.x,但不能保证该版本或将来的版本:

(function() 
    'use strict';

    angular
        .module('your.module.name.goes.here')
        .config(configBlock);

    /** @ngInject */
    configBlock.$inject = ['$provide'];
    function configBlock($provide) 
        $provide.decorator('$q', ['$delegate', function ($delegate) 
            console.log($delegate);
            var Promise = $delegate.prototype.constructor;

            Promise.prototype.inspect = function () 
                var inspect = ;
                switch (this.$$state.status) 
                    case -1:
                    case 0:
                        inspect.state = 'pending';
                        break;
                    case 1:
                        inspect.state = 'fulfilled';
                        break;
                    case 2:
                        inspect.state = 'rejected';
                        break;
                    default:
                        inpsect.state = 'unknown';
                
                return inspect;
            ;

            Promise.prototype.isFulfilled = function () 
                return this.inspect().state === 'fulfilled';
            
            Promise.isFulfilled = function (obj) 
                if (obj.constructor !== Promise) 
                    return true;
                
                return obj.isFulfilled();
            

            Promise.prototype.isRejected = function () 
                return this.inspect().state === 'rejected';
            
            Promise.isRejected = function (obj) 
                if (obj.constructor !== Promise) 
                    return false;
                
                return obj.isRejected();
            

            Promise.prototype.isPending = function () 
                return this.inspect().state === 'pending';
            
            Promise.isPending = function (obj) 
                if (obj.constructor !== Promise) 
                    return false;
                
                return obj.isPending();
            

            return $delegate;
        ]);
    
)();

【讨论】:

以上是关于获取 Angular 的延迟状态?的主要内容,如果未能解决你的问题,请参考以下文章

在 Angular 中等待用户交互时,可替代延迟(反?-)模式

如何在 Angular js 的选项卡集中启用延迟加载?

Angular Karma Jasmine 错误:非法状态:无法加载指令摘要

Angular - ui-router 获取先前的状态

Angular 无法获取响应状态文本

Angular 2、TypeScript 和 ui-router - 如何获取状态参数