将$http替换为提取 API



我正在用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.

最新更新