获取 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 deferred 和promise 对象都将有一个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
做出的承诺,并不是每个承诺都是从该方法生成的。具体来说,reject
、when
/resolve
和 all
方法在内部专门调用 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 中等待用户交互时,可替代延迟(反?-)模式