Skip to content

Commit

Permalink
[FEATURE] Adds public API for loadData promise
Browse files Browse the repository at this point in the history
- Adds an additional Promise to support checking for the data loading state.
Currently, if the JSONModel was created from the manifest, applications
cannot check if the data was loaded yet during the
controller init.
Attaching to the requestCompleted events etc. might be too late,
depending on the timing of the data requests.

- Also adds a Promise as the return value of the loadData() function.

Change-Id: I99d1079b907bc156efaa833c7d20b1e04e0451b2
Fixes: #2213
Fixes: #2282
BCP: 1880574213
  • Loading branch information
Thodd committed Feb 19, 2019
1 parent b4d75fc commit bd4e2fc
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 8 deletions.
37 changes: 32 additions & 5 deletions src/sap.ui.core/src/sap/ui/model/json/JSONModel.js
Expand Up @@ -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){
Expand Down Expand Up @@ -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) {
Expand All @@ -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
*
Expand Down
200 changes: 198 additions & 2 deletions src/sap.ui.core/test/sap/ui/core/qunit/json/JSONModel.qunit.js
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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();
Expand Down
Expand Up @@ -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!
Expand Down

0 comments on commit bd4e2fc

Please sign in to comment.