我正在用Fetch API替换$http
,并被$q
替换为Promise API。正因为如此,Angular 不再运行摘要周期,因此 UI 没有呈现。为了解决这个问题,我尝试了Zone.js这似乎部分解决了我们的问题。不幸的是,它的 API 在 0.6 中完全更改,因此我们使用旧版 0.5.15。
现在进入实际问题。
刷新页面时,Angular 会像预期的那样配置和引导应用程序。在此阶段,我将初始化区域并使用区域和$rootScope.$digest()
装饰$rootScope.apply
。现在,当我在状态/路由之间转换(使用 ui-router(时,一切都按预期工作,但是当完全刷新时,会出现争用条件并且区域/摘要无法正常运行。我不知道如何解决它。
我在angular.run()
块中有以下代码:
console.log('Zone setup begin');
const scopePrototype = $rootScope.constructor.prototype;
const originalApply = scopePrototype.$apply;
const zoneOptions = {
afterTask: function afterTask() {
try {
$rootScope.$digest();
} catch (e) {
$exceptionHandler(e);
throw e;
}
}
};
scopePrototype.$apply = function $applyFn() : void {
const scope = this;
const applyArgs = arguments;
window.zone.fork(zoneOptions).run(() => {
originalApply.apply(scope, applyArgs);
console.log('Zone + $digest run!');
});
};
console.log('Zone setup end');
在上面你可以看到,当区域初始化开始、结束和运行时,我登录到控制台(+角度摘要周期(。在我的控制器中,我通过 Fetch API 获取数据,我添加了一个console.log('Data fetched!');
,以便我知道何时获取数据。
现在控制台中的输出:
使用 ui 路由器进行状态转换(完美运行(
请注意,摘要在最后运行。
Zone setup begin
Zone setup end
Zone + $digest run!
Zone + $digest run!
Zone + $digest run!
Zone + $digest run!
Data fetched!
Zone + $digest run!
状态/路由上的完全刷新(最终不会运行(
Zone setup begin
Zone setup end
Zone + $digest run!
Zone + $digest run!
Zone + $digest run!
Zone + $digest run!
Data fetched!
如您所见,区域/摘要在获取数据后不会运行,这就是数据和 UI 未在页面上呈现的原因。
将fetch API 创建的 ES6 承诺转换为 AngularJS $q $q.when 的承诺。
使用$q.when
将 ES6 承诺转换为 AngularJS 承诺1
AngularJS通过提供自己的事件处理循环来修改正常的JavaScript流。这将JavaScript分为经典和AngularJS执行上下文。只有在 AngularJS 执行上下文中应用的操作才能从 AngularJS 数据绑定、异常处理、属性监视等中受益。2由于承诺来自 AngularJS 框架之外,因此框架不知道模型的变化,也不会更新 DOM。
使用$q.when
将外部承诺转换为 Angular 框架承诺:
var myRequest = new Request('flowers.jpg');
$q.when(fetch(myRequest)).then(function(response) {
//code here
})
使用与 AngularJS 框架及其摘要周期正确集成$q服务承诺。
$q.何时
将可能是值或(第三方(当时可承诺的对象包装为
$q
承诺。当您处理的对象可能是也可能不是承诺时,或者如果承诺来自不可信的源,这很有用。-- AngularJS $q Service API 参考 - $q.when
基本原理
包装$q.when
会起作用,但根据我团队的经验,它会非常挑剔且容易出错。例如,从Promise.then
函数主体内部返回$q.when
仍将作为常规Promise
链接,并且不会在回调时获得$digest。
它还要求所有作者了解两个外观非常相似的构造(Promise/$q(之间的区别,并关心异步调用的每个级别的具体类型。如果您使用的是现代便利设施,例如async
/await
(它进一步抽象了 Promise 类型(,您将遇到更多麻烦。突然之间,你的代码都不能与框架无关。
我们的团队决定值得提交一个大的猴子补丁,以确保所有承诺(以及async
/await
关键字("刚刚奏效",而无需额外的思考。
丑?是的。但我们觉得这是一个可以的权衡。
补丁承诺回调始终适用$rootScope
首先,我们将针对Promise
的补丁安装在angular.run
块中:
angular.module(...).run(normalizePromiseSideEffects);
normalizePromiseSideEffects.$inject = ['$rootScope'];
function normalizePromiseSideEffects($rootScope) {
attachScopeApplicationToPromiseMethod('then');
attachScopeApplicationToPromiseMethod('catch');
attachScopeApplicationToPromiseMethod('finally');
function attachScopeApplicationToPromiseMethod(methodName) {
const NativePromiseAPI = window.Promise;
const nativeImplementation = NativePromiseAPI.prototype[methodName];
NativePromiseAPI.prototype[methodName] = function(...promiseArgs) {
const newPromiseArgs = promiseArgs.map(wrapFunctionInScopeApplication);
return nativeImplementation.bind(this)(...newPromiseArgs);
};
}
function wrapFunctionInScopeApplication(fn) {
if (!isFunction(fn) || fn.isScopeApplicationWrapped) {
return fn;
}
const wrappedFn = (...args) => {
const result = fn(...args);
// this API is used since it's $q was using in AngularJS src
$rootScope.$evalAsync();
return result;
};
wrappedFn.isScopeApplicationWrapped = true;
return wrappedFn;
}
}
异步/等待
如果你想支持使用async
/await
,你还需要配置 Babel 以始终将语法实现为 Promises。我们用了babel-plugin-transform-async-to-promises
.