Promise
With the help of Promises, you can easily
- handle asynchronous methods
- control the execution of async methods
- in parallel
- sequential
- control the flow of async methods in success / error cases
- enhance the UX with loading indicators
- …
If you are new to Promises, please make yourself familiar before continuing:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
All OData calls used in this article refer to OData v2.
Basic OData call
This is a very basic OData call where the key and the callbacks are hardcoded.
var model = that.getView().getModel();
model.read("/MyEntitySet('12345')", {
success: function (data, response) {
// success handler
},
error: function (error) {
// error handler
}
});
Promisified OData call
A wrapped version of the basic OData call.
The key is given as parameter and the wrapping method returns a promise. The resolve
/ reject
methods are used in the success
/ error
callbacks to resolve or reject the Promise.
As the resolve
method takes only one parameter, data and response are passed as object.
function readMyEntity(id) {
return new Promise(function (resolve, reject) {
var model = that.getView().getModel();
model.read("/MyEntitySet('" + id + "')", {
success: function (data, response) {
resolve({ data: data, response: response });
},
error: function (error) {
reject(error);
}
});
});
}
The success and error handling can now be done after invoking the method. This is done by applying the Promise Chain Pattern.
readMyEntity(12345)
.then(function (result) {
// success handler
console.log(result.data, result.response);
})
.catch(function (error) {
// error handler
console.error(error);
});
Refactor the Promisified OData call
Now, let’s make the OData call more robust for everyday usage:
- hand over the whole entity as object and construct the key from its data
- provide an option to add additional data like filters to the call (
mParameters
as described in the documentation) - make sure its not possible to provide
success
/error
callbacks with the parameters object as this would break the promise functionality
The UI5 framework detects when the same OData call is done multiple times. It does only one request and returns the same result object for every call. If this result object is being changed, it changes for all calls. To prevent this from happening, the result is being copied with Object.assign()
.
If you plan to use this with IE11, make sure to provide the Object.assign Polyfill. In case the Polyfill is not option for you, a simple JSON.parse(JSON.stringify(data))
could do the trick as well.
/**
* Returns the result of the OData call
* @param {object} payload - The payload which equals to the entity
* @param {object} parameters - The parameters like Filters added to the OData call
* @returns {Promise} Promise object represents result of the OData call
*/
function readMyEntity(payload, parameters) {
var model = this.getView().getModel();
parameters = parameters || {};
return model.metadataLoaded()
.then(function () {
return new Promise(function (resolve, reject) {
var key = model.createKey("/MyEntitySet", payload)
// prevent success / error callbacks to be overwritten
var params = Object.assign({}, parameters,
{
success: function (data, response) {
// prevent accidently change of response data for subsequent calls
var dataCopy = Object.assign({}, data);
resolve({ data: dataCopy, response: response });
},
error: function (error) {
// additional error handling when needed
reject(error);
}
});
model.read(key, params);
// update could look like this:
// model.update(key, payload, params);
// create could look like this:
// model.create("/MyEntitySet", payload, params);
// delete could look like this:
// model.remove(key, params);
})
})
}
readMyEntity({ id: 12345 })
.then(function (result) {
console.log(result.data, result.response);
})
.catch(function (error) {
console.error(error);
});
Chaining multiple OData calls together
Breaks after the first Promise which does not resolve.
The finally
method is not supported by the Promise Polyfill for IE11.
when you call Promises within a then, return their result and continue with it from the next chained then. Beside Promises, you can also return static values.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#chaining
myElement.setBusy(true);
readMyEntity({ id: 12345 })
.then(function (result) {
console.log(result.data, result.response);
return readMyEntity({ id: 67890 })
})
.then(function (result) {
console.log(result.data, result.response);
return readMyEntity({ id: 34567 })
})
.then(function (result) {
console.log(result.data, result.response);
})
.catch(function (error) {
console.error(error);
})
.finally(function () {
myElement.setBusy(false);
});
Run multiple OData calls in parallel
Promise.all
Breaks with the first Promise that is rejected. The results are ordered the same way your promises where.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Promise.all([
readMyEntity({ id: 12345 }),
readMyEntity({ id: 67890 })
])
.then(function (results) {
console.log(results);
// success handler
})
.catch(function (error) {
console.error(error);
// error handler
});
Promise.allSettled
Waits until all Promises are fullfilled/rejected and tells the status as well as the result.
For two Promises where the first resolves and the second gets rejected, this could like like this:
// example result with two promises
[
{status: "fulfilled", value: ...},
{status: "rejected", reason: ...}
]
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
Promise.allSettled([
readMyEntity({ id: 12345 }),
readMyEntity({ id: 67890 })
])
.then(function (results) {
console.log(results);
// success / error handler
});