SAP UI5中Promise失效导致应用偶发崩溃问题求助
解决SAP UI5中Promise未生效导致Formatter找不到数据的问题
我来帮你分析并解决这个问题——你遇到的核心问题是Promise没有按预期控制数据加载与视图渲染的顺序,加上代码里的几处小错误,导致偶发的formatter报错和页面冻结。
先理清楚你遇到的现象:
- 异常情况下,后续视图的formatter在OData请求完成前就触发了,找不到数据报错
- 正常情况下,所有数据加载完成后才会执行formatter,页面正常显示
问题根源分析
- 明显的回调错误:你自己也发现了,
/External_OrganizationsSet的success回调误用了oModelRefDataDeferred.resolve(),而不是对应的oModelExtOrgDataDeferred。这会导致这个Deferred永远不会被resolve,jQuery.when会一直处于等待状态,后续视图加载时数据自然没准备好。 - Deferred作用域问题:你把Deferred变量定义在
sap.ui.define的回调外层,这会让它们变成模块级的全局变量。如果应用有路由导航导致视图重建,这些Deferred可能已经处于resolved状态,不会重新触发,逻辑直接混乱。 - Promise使用不规范:
jQuery.when(...).done().then(...)的写法冗余,而且没有处理错误场景——如果某个OData请求失败,对应的Deferred没被reject,jQuery.when会一直pending,直接卡死应用。 - 视图加载时机未受控:即使Promise正常工作,你也没控制后续视图(比如Worklist)的加载时机,它可能在数据准备好之前就渲染了,导致formatter提前执行。
一步步解决问题
1. 先修复回调的低级错误
把/External_OrganizationsSet的success回调改成正确的Deferred,同时错误时也要reject,避免Promise一直挂着:
sPath = "/External_OrganizationsSet"; oModel.read(sPath, { success: function(oData, oResponse) { oModelExtOrgDataDeferred.resolve(); // 这里换成正确的Deferred }, error: function(oError) { jQuery.sap.log.error("Error loading External Organizations", oError); oModelExtOrgDataDeferred.reject(oError); // 错误时必须reject } });
2. 调整Deferred的作用域
把所有Deferred变量移到onInit函数内部,避免模块级变量带来的重复初始化问题:
onInit: function () { // 把Deferred移到这里,每次初始化控制器都会创建新的Deferred var oModelTypeDataDeferred = jQuery.Deferred(); var oModelStatusDataDeferred = jQuery.Deferred(); var oModelRoleDataDeferred = jQuery.Deferred(); var oModelRefDataDeferred = jQuery.Deferred(); var oModelExtOrgDataDeferred = jQuery.Deferred(); var oModelInvolvementDataDeferred = jQuery.Deferred(); // 剩余代码... }
3. 用Promise控制视图渲染时机
为了确保formatter只在数据就绪后执行,我们可以给appView模型加一个dataReady标志,formatter先判断这个标志再执行逻辑:
首先在readyToGo里设置标志:
var readyToGo = function() { jQuery.sap.log.error("Ready!"); // 告诉所有视图数据已经准备好 this.getModel("appView").setProperty("/dataReady", true); fnSetAppNotBusy(); };
然后在formatter里做判断:
// 示例formatter函数 sap.ui.define([], function() { return { formatAgreementType: function(sType) { var oAppModel = this.getView().getModel("appView"); // 数据未就绪时返回空或者占位符,避免报错 if (!oAppModel.getProperty("/dataReady")) { return ""; } // 正常的格式化逻辑 var oF4Model = this.getView().getModel("f4"); var aTypes = oF4Model.getProperty("/Agreement_TypesSet"); // ... 你的格式化代码 } }; });
4. 优化Promise封装(可选但推荐)
手动创建Deferred比较繁琐,我们可以把OData read封装成原生Promise,代码更简洁易维护:
// 封装OData read为Promise函数 var readOData = function(oModel, sPath) { return new Promise(function(resolve, reject) { oModel.read(sPath, { success: resolve, error: function(oError) { jQuery.sap.log.error(`Failed to load ${sPath}`, oError); reject(oError); } }); }); }; // 替换原来的多个read请求和jQuery.when oModel.metadataLoaded().then(function(){ Promise.all([ readOData(oModel, "/Agreement_TypesSet"), readOData(oModel, "/Agreement_StatusesSet"), readOData(oModel, "/Role_TypesSet"), readOData(oModel, "/Reference_TypesSet"), readOData(oModel, "/External_OrganizationsSet"), readOData(oModel, "/Involvement_TypesSet") ]).then(function(results) { // 可选:把返回的数据直接存入f4模型,方便formatter获取 oModel.setProperty("/Agreement_TypesSet", results[0]); oModel.setProperty("/Agreement_StatusesSet", results[1]); oModel.setProperty("/Role_TypesSet", results[2]); oModel.setProperty("/Reference_TypesSet", results[3]); oModel.setProperty("/External_OrganizationsSet", results[4]); oModel.setProperty("/Involvement_TypesSet", results[5]); readyToGo.call(this); }.bind(this)).catch(function(oError) { jQuery.sap.log.error("Data loading failed", oError); fnSetAppNotBusy(); }); }.bind(this));
5. 确保路由配置正确(补充)
检查manifest.json里的路由配置,确保初始路由(Worklist)是异步加载的,并且根视图的初始化完成后才会渲染:
"sap.ui5": { "routing": { "config": { "routerClass": "sap.m.routing.Router", "viewType": "XML", "viewPath": "kristal.apps.agreements.view", "controlId": "app", "controlAggregation": "pages", "async": true // 确保视图异步加载 }, "routes": [ { "pattern": "", "name": "worklist", "target": "worklist" } ], "targets": { "worklist": { "viewName": "Worklist", "viewLevel": 1 } } } }
最终修正后的完整控制器代码
sap.ui.define([ "kristal/apps/agreements/controller/BaseController", "sap/ui/model/json/JSONModel" ], function (BaseController, JSONModel) { "use strict"; return BaseController.extend("kristal.apps.agreements.controller.App", { onInit: function () { var readyToGo = function() { jQuery.sap.log.error("Ready!"); this.getModel("appView").setProperty("/dataReady", true); fnSetAppNotBusy(); }; var oViewModel = new JSONModel({ busy: true, delay: 0, dataReady: false }); this.setModel(oViewModel, "appView"); var iOriginalBusyDelay = this.getView().getBusyIndicatorDelay(); var fnSetAppNotBusy = function() { oViewModel.setProperty("/busy", false); oViewModel.setProperty("/delay", iOriginalBusyDelay); }; this.getOwnerComponent().getModel().metadataLoaded().then(fnSetAppNotBusy); var oModel = this.getOwnerComponent().getModel("f4"); // 封装OData read为Promise var readOData = function(oModel, sPath) { return new Promise(function(resolve, reject) { oModel.read(sPath, { success: resolve, error: function(oError) { jQuery.sap.log.error(`Failed to load ${sPath}`, oError); reject(oError); } }); }); }; oModel.metadataLoaded().then(function(){ Promise.all([ readOData(oModel, "/Agreement_TypesSet"), readOData(oModel, "/Agreement_StatusesSet"), readOData(oModel, "/Role_TypesSet"), readOData(oModel, "/Reference_TypesSet"), readOData(oModel, "/External_OrganizationsSet"), readOData(oModel, "/Involvement_TypesSet") ]).then(function(results) { // 将数据存入模型 oModel.setProperty("/Agreement_TypesSet", results[0]); oModel.setProperty("/Agreement_StatusesSet", results[1]); oModel.setProperty("/Role_TypesSet", results[2]); oModel.setProperty("/Reference_TypesSet", results[3]); oModel.setProperty("/External_OrganizationsSet", results[4]); oModel.setProperty("/Involvement_TypesSet", results[5]); readyToGo.call(this); }.bind(this)).catch(function(oError) { jQuery.sap.log.error("Data loading failed", oError); fnSetAppNotBusy(); }); }.bind(this)); } // 其他控制器方法... }); });
内容的提问来源于stack exchange,提问作者Greg
相关产品推荐
相关产品推荐

