diff --git a/src/sap.ui.core/src/sap/ui/model/json/JSONModel.js b/src/sap.ui.core/src/sap/ui/model/json/JSONModel.js index 0b6984f275f4..d9eaf3e347ef 100644 --- a/src/sap.ui.core/src/sap/ui/model/json/JSONModel.js +++ b/src/sap.ui.core/src/sap/ui/model/json/JSONModel.js @@ -192,6 +192,7 @@ sap.ui.define([ * @param {boolean} [bCache=true] Disables caching if set to false. Default is true. * @param {object} [mHeaders] An object of additional header key/value pairs to send along with the request * + * @return {Promise|undefined} in case bAsync is set to true a Promise is returned; this promise resolves/rejects based on the request status * @public */ JSONModel.prototype.loadData = function(sURL, oParameters, bAsync, sType, bMerge, bCache, mHeaders){ @@ -233,6 +234,10 @@ sap.ui.define([ this.fireRequestCompleted({url : sURL, type : sType, async : bAsync, headers: mHeaders, info : "cache=" + bCache + ";bMerge=" + bMerge, infoObject: {cache : bCache, merge : bMerge}, success: false, errorobject: oError}); this.fireRequestFailed(oError); + + if (bAsync) { + return Promise.reject(oError); + } }.bind(this); var _loadData = function(fnSuccess, fnError) { @@ -257,17 +262,39 @@ sap.ui.define([ _loadData(resolve, fnReject); }); - this.pSequentialImportCompleted = this.pSequentialImportCompleted.then(function() { - //must always resolve - return pImportCompleted.then(fnSuccess, fnError).catch(function(oError) { - Log.error("Loading of data failed: " + oError.stack); - }); + // chain the existing loadData calls, so the import is done sequentially + var pReturn = this.pSequentialImportCompleted.then(function() { + return pImportCompleted.then(fnSuccess, fnError); }); + + // attach exception/rejection handler, so the internal import promise always resolves + this.pSequentialImportCompleted = pReturn.catch(function(oError) { + Log.error("Loading of data failed: " + oError.stack); + }); + + // return chained loadData promise (sequential imports) + // but without a catch handler, so the application can also is notified about request failures + return pReturn; } else { _loadData(fnSuccess, fnError); } }; + /** + * Returns a Promise of the current data-loading state. + * Every currently running {@link sap.ui.model.json.JSONModel#loadData} call is respected by the returned Promise. + * This also includes a potential loadData call from the JSONModel's constructor in case a URL was given. + * The data-loaded Promise will resolve once all running requests have finished. + * Only request, which have been queued up to the point of calling + * this function will be respected by the returned Promise. + * + * @return {Promise} a Promise, which resolves if all pending data-loading requests have finished + * @public + */ + JSONModel.prototype.dataLoaded = function() { + return this.pSequentialImportCompleted; + }; + /** * @see sap.ui.model.Model.prototype.bindProperty * diff --git a/src/sap.ui.core/test/sap/ui/core/qunit/json/JSONModel.qunit.js b/src/sap.ui.core/test/sap/ui/core/qunit/json/JSONModel.qunit.js index 4cf52df01d26..5f58398a0ae4 100644 --- a/src/sap.ui.core/test/sap/ui/core/qunit/json/JSONModel.qunit.js +++ b/src/sap.ui.core/test/sap/ui/core/qunit/json/JSONModel.qunit.js @@ -471,7 +471,19 @@ sap.ui.define([ }); }); - QUnit.test("test JSONModel loadData: multiple requests - merge",function(assert){ + QUnit.test("test JSONModel loadData: dataLoaded() [async, Promise(chained)]",function(assert){ + var done = assert.async(); + var testModel = new JSONModel(); + testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata.json"); + testModel.dataLoaded().then(function() { + assert.equal(testModel.getProperty("/foo"), "The quick brown fox jumps over the lazy dog."); + assert.equal(testModel.getProperty("/bar"), "ABCDEFG"); + assert.equal(testModel.getProperty("/baz")[1], 97); + done(); // resume normal testing + }); + }); + + QUnit.test("test JSONModel loadData [async, event]: multiple requests - merge",function(assert){ var done = assert.async(); var testModel = new JSONModel(); var loadCount = 0; @@ -494,7 +506,75 @@ sap.ui.define([ }); }); - QUnit.test("test JSONModel loadData: multiple requests",function(assert){ + QUnit.test("test JSONModel loadData [async, Promise(chained)]: multiple requests - merge",function(assert){ + var done = assert.async(); + var testModel = new JSONModel(); + testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata.json"); + testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata2.json",null,true,null,true); + // once the Promise resolves, everything is already merged + testModel.dataLoaded().then(function() { + assert.equal(testModel.getProperty("/foo"), "The quick brown fox jumps over the lazy dog."); + assert.equal(testModel.getProperty("/bar"), "ABCDEFGHIJ"); + assert.equal(testModel.getProperty("/baz")[1], 97); + assert.equal(testModel.getProperty("/merged"), true); + done(); + }); + }); + + QUnit.test("test JSONModel loadData [async, event & Promise]: multiple requests - merge",function(assert){ + var done = assert.async(); + var testModel = new JSONModel(); + var loadCount = 0; + testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata.json"); + testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata2.json",null,true,null,true); + // one event handler call for each loadData call + testModel.attachRequestCompleted(function() { + loadCount++; + if (loadCount == 1) { + assert.equal(testModel.getProperty("/foo"), "The quick brown fox jumps over the lazy dog."); + assert.equal(testModel.getProperty("/bar"), "ABCDEFG"); + assert.equal(testModel.getProperty("/baz")[1], 97); + } else { + assert.equal(testModel.getProperty("/foo"), "The quick brown fox jumps over the lazy dog."); + assert.equal(testModel.getProperty("/bar"), "ABCDEFGHIJ"); + assert.equal(testModel.getProperty("/baz")[1], 97); + assert.equal(testModel.getProperty("/merged"), true); + } + }); + // Only one promise for ALL loadData calls; + // resolve: everything is already merged, the intermediate states are no seen anymore + testModel.dataLoaded().then(function() { + assert.equal(testModel.getProperty("/foo"), "The quick brown fox jumps over the lazy dog."); + assert.equal(testModel.getProperty("/bar"), "ABCDEFGHIJ"); + assert.equal(testModel.getProperty("/baz")[1], 97); + assert.equal(testModel.getProperty("/merged"), true); + done(); + }); + }); + + QUnit.test("test JSONModel loadData [async, Promise(single)]: multiple requests - merge",function(assert){ + var done = assert.async(); + var testModel = new JSONModel(); + + var pLoad1 = testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata.json").then(function() { + assert.equal(testModel.getProperty("/foo"), "The quick brown fox jumps over the lazy dog."); + assert.equal(testModel.getProperty("/bar"), "ABCDEFG"); + assert.equal(testModel.getProperty("/baz")[1], 97); + }); + + var pLoad2 = testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata2.json",null,true,null,true).then(function() { + assert.equal(testModel.getProperty("/foo"), "The quick brown fox jumps over the lazy dog."); + assert.equal(testModel.getProperty("/bar"), "ABCDEFGHIJ"); + assert.equal(testModel.getProperty("/baz")[1], 97); + assert.equal(testModel.getProperty("/merged"), true); + }); + + Promise.all([pLoad1, pLoad2]).then(function() { + done(); + }); + }); + + QUnit.test("test JSONModel loadData [async, event]: multiple requests - no merge",function(assert){ var done = assert.async(); var testModel = new JSONModel(); var loadCount = 0; @@ -517,6 +597,122 @@ sap.ui.define([ }); }); + QUnit.test("test JSONModel loadData [async, Promise]: multiple requests - no merge",function(assert){ + var done = assert.async(); + var testModel = new JSONModel(); + testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata.json"); + testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata2.json"); + testModel.dataLoaded().then(function() { + assert.ok(!testModel.getProperty("/foo"), "deleted as no merge"); + assert.equal(testModel.getProperty("/bar"), "ABCDEFGHIJ"); + assert.ok(!testModel.getProperty("/baz"), "deleted as no merge"); + assert.equal(testModel.getProperty("/merged"), true); + done(); + }); + }); + + QUnit.test("test JSONModel loadData [async, event & Promise]: multiple requests - no merge",function(assert){ + var done = assert.async(); + var testModel = new JSONModel(); + var loadCount = 0; + testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata.json"); + testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata2.json"); + + // one event handler call for each loadData call + testModel.attachRequestCompleted(function() { + loadCount++; + if (loadCount == 1) { + assert.equal(testModel.getProperty("/foo"), "The quick brown fox jumps over the lazy dog."); + assert.equal(testModel.getProperty("/bar"), "ABCDEFG"); + assert.equal(testModel.getProperty("/baz")[1], 97); + } else { + assert.ok(!testModel.getProperty("/foo"), "deleted as no merge"); + assert.equal(testModel.getProperty("/bar"), "ABCDEFGHIJ"); + assert.ok(!testModel.getProperty("/baz"), "deleted as no merge"); + assert.equal(testModel.getProperty("/merged"), true); + } + }); + + // Only one promise for ALL loadData calls; + // resolve: everything is already merged, the intermediate states are no seen anymore + testModel.dataLoaded().then(function() { + assert.ok(!testModel.getProperty("/foo"), "deleted as no merge"); + assert.equal(testModel.getProperty("/bar"), "ABCDEFGHIJ"); + assert.ok(!testModel.getProperty("/baz"), "deleted as no merge"); + assert.equal(testModel.getProperty("/merged"), true); + done(); + }); + }); + + QUnit.test("test JSONModel loadData [async, Promise(single)]: multiple requests - no merge",function(assert){ + var done = assert.async(); + var testModel = new JSONModel(); + + var p1 = testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata.json").then(function() { + assert.equal(testModel.getProperty("/foo"), "The quick brown fox jumps over the lazy dog."); + assert.equal(testModel.getProperty("/bar"), "ABCDEFG"); + assert.equal(testModel.getProperty("/baz")[1], 97); + }); + + var p2 = testModel.loadData("test-resources/sap/ui/core/qunit/json/data/testdata2.json").then(function() { + assert.ok(!testModel.getProperty("/foo"), "deleted as no merge"); + assert.equal(testModel.getProperty("/bar"), "ABCDEFGHIJ"); + assert.ok(!testModel.getProperty("/baz"), "deleted as no merge"); + assert.equal(testModel.getProperty("/merged"), true); + }); + + // Only one promise for ALL loadData calls; + // resolve: everything is already merged, the intermediate states are no seen anymore + Promise.all([p1, p2]).then(function() { + assert.ok(!testModel.getProperty("/foo"), "deleted as no merge"); + assert.equal(testModel.getProperty("/bar"), "ABCDEFGHIJ"); + assert.ok(!testModel.getProperty("/baz"), "deleted as no merge"); + assert.equal(testModel.getProperty("/merged"), true); + done(); + }); + }); + + QUnit.test("test JSONModel loadData [async, Promise(chained)]: multiple requests with merge: 1. request slow",function(assert){ + var done = assert.async(); + var testModel = new JSONModel(); + + testModel.loadData("/fake/testdata3.json"); + testModel.loadData("/fake/testdata4.json"); + testModel.dataLoaded().then(function(oInfo) { + assert.equal(testModel.getProperty("/foo"), "The quick brown fox jumps over the lazy dog."); + assert.equal(testModel.getProperty("/bar"), "ABCDEFGHIJ"); + assert.equal(testModel.getProperty("/baz")[1], 97); + assert.equal(testModel.getProperty("/merged"), true); + done(); + }); + }); + + QUnit.test("test JSONModel loadData [async, Promise]: error during parse",function(assert){ + assert.expect(7); + var done = assert.async(); + var testModel = new JSONModel(); + + var p1 = testModel.loadData("/fake").catch(function(oError) { + assert.equal(oError.message, "parsererror", "parse error leads to rejection - 1"); + assert.equal(oError.responseText, "ERROR!", "parse error leads to rejection - 1"); + }); + + var p2 = testModel.loadData("/fake/broken.json").catch(function(oError) { + assert.equal(oError.message, "parsererror", "parse error leads to rejection - 2"); + assert.equal(oError.responseText, '{"foo": "The quick brown fox jumps over the lazy dog.","bar": "ABCDEFGHIJ""baz": [52, 97]}', "parse error leads to rejection - 2"); + }); + + var p3 = testModel.loadData("/fake/testdata4.json").then(function() { + assert.equal(testModel.getProperty("/foo"), "The quick brown fox jumps over the lazy dog."); + assert.equal(testModel.getProperty("/bar"), "ABCDEFGHIJ"); + assert.equal(testModel.getProperty("/baz")[1], 97); + }); + + Promise.all([p1, p2, p3]).then(function() { + done(); + }); + }); + QUnit.test("test JSONModel loadData: multiple requests with merge: 1. request slow",function(assert){ var done = assert.async(); var testModel = new JSONModel(); diff --git a/src/sap.ui.core/test/sap/ui/core/qunit/json/data/JSONModelFakeService.js b/src/sap.ui.core/test/sap/ui/core/qunit/json/data/JSONModelFakeService.js index 8047f48cc91d..d76bd1e3431c 100644 --- a/src/sap.ui.core/test/sap/ui/core/qunit/json/data/JSONModelFakeService.js +++ b/src/sap.ui.core/test/sap/ui/core/qunit/json/data/JSONModelFakeService.js @@ -39,7 +39,14 @@ sap.ui.define([ '"baz": [52, 97],' + '"merged": true' + '}'; - break; + break; + case "/fake/broken.json": + sAnswer = '{' + + '"foo": "The quick brown fox jumps over the lazy dog.",' + + '"bar": "ABCDEFGHIJ"' + // missing "," + '"baz": [52, 97]' + + '}'; + break; default: // No dummy request!