We recommend to use apprt-core/Promise
in cases where async functionality is required.
The apprt-core/Promise
class is API-compatible to the
JavaScript Promises,
but provides some additional features.
A Promise is modeling async operation states and can have the following internal states:
pending
: Initial state, neither fulfilled
nor rejected
.fulfilled
: Operation successful.rejected
: Operation failed.A rejected
Promise, whose rejection result is derived from apprt-core/Cancel
, is named cancelled
.
For more information about cancellation, see Cancellation.
If a method produces async results, it should return a Promise.
import Promise from "apprt-core/Promise"
function delayedTask(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
try{
resolve(doIt());
}catch(e){
reject(e);
}
},100);
});
}
The Promise constructor accepts an executor function, which is called by the two parameters resolve
and reject
.
resolve
function is used to mark the Promise as successfully fulfilled.reject
function is used to mark the Promise as rejected by an error and should be called with an Error instance as parameter.function doSomething(){
let promise = delayedTask();
promise.then((result)=> {
// do something with the result
});
}
The Promise.prototype.then
method is used to append handlers to a Promise.
A success handler is the first argument of the then
function and is executed, if the Promise is fulfilled.
The argument of the success handler is the argument that is passed to the resolve
function of the Promise.
then
function doSomething(){
let promise = delayedTask();
promise.then(successHandler,(error)=> {
// do something with the error
});
}
The Promise.prototype.then
method is used to append handlers to a Promise.
An error handler is the second argument of the then
function and executed, if the Promise is rejected.
The argument of the error handler is the argument that is passed to the reject
function of the Promise.
catch
function doSomething(){
let promise = delayedTask();
promise.catch((error)=> {
// do something with the error
});
}
The Promise.prototype.catch
method is used to append error handlers to a Promise.
The argument of the error handler is the argument that is passed to the reject
function of the Promise.
Its usage is more explicit than the usage of the then
method and
especially useful for readability in chains of handler functions.
else
function doSomething(){
let promise = delayedTask();
promise.else((error)=> {
// do something with the error
});
}
The Promise.prototype.else
method has the same purpose as the Promise.prototype.catch
method,
but in contrast to catch
it is not called when the rejection reason is an instance of apprt-core/Cancel
.
This means that the error handler is not called, if the Promise is cancelled.
To execute a handler regardless of the fulfilled or rejected state of a Promise (for example to clean up some state), use the finally
method:
function doSomething(){
let promise = delayedTask();
promise.finally(()=> {
// do something
});
}
The Promise.prototype.finally
method is used to append a handler to a Promise, which should be called on both, fulfilled and rejected.
finally
is semantically equal to the registration of a handler with then
, such as success or error handler.
To build a fluent tree of async actions, chain the methods then
, catch
, else
and finally
.
function doSomething(){
let promise = delayedTask();
promise
.then(parseResult)
.then(updateStore)
.then(updateUI)
.else(showUIError)
.finally(cleanUp);
}
The result of a previous handler is the result or error argument of the next handler.
Sample:
If parseResult
throws an error, the Promises of the next handlers are moved to rejection state.
function parseResult(text){
if (text !== "ok"){
throw new Error("wrong text");
} else {
return { newState: "ok" };
}
}
Now assume, that the result of 'delayedTask' is 'not ok', which means that only the error handler 'showUIError' and the finally handler 'cleanUp' are called. The handlers 'updateStore' and 'updateUI' are not called because they are registered as success handlers.
When the result of a previous handler is a Promise, the execution of the next handler is delayed, until the Promise is rejected or fulfilled, so that the result of the Promise can be passed as argument to the next handler.
Sample:
import Promise from "apprt-core/Promise"
function parseResult(text){
return new Promise(resolve => {
...
resolve({ newState: "ok" });
});
}
After some time updateStore
is called with the argument { newState: "ok" }
,
because it is the result of the previous Promise.
To create Promise instances directly, because a result is already available but the API should be asynchronous, see the following samples.
import Promise from "apprt-core/Promise"
function retrieveMessage(){
let msg = "a message";
return Promise.resolve(msg);
}
import Promise from "apprt-core/Promise"
function produceWrongParameterError(){
return Promise.reject(new Error("wrong parameter"));
}
Asking a Promise for its resolution state is a rare case and not supported by the official JavaScript Promise API.
To achieve this anyway, the apprt-core/Promise
provides the trackState
method to add some more methods to a Promise instance.
The methods appended by trackState
are:
isSettled
: true, if Promise is not pendingisFulfilled
: true, if Promise is resolved (NOTE: this is not like dojo/Deferred.isFulfilled
, which has isSettled
semantics)isRejected
: true, if Promise is rejectedisCancelled
: true, if Promise is rejected by a apprt-core/Cancel
instanceSample:
function checkStateOfAPromise(promise){
promise = promise.trackState();
// NOTE: a direct use of promise.is* methods may work, too.
// But because of the async behavior of Promises this cannot always be guaranteed.
promise.finally(()=>{
if (promise.isRejected()){
// error
}else if(promise.isFulfilled()){
// success
}
});
}
To wait for multiple Promises before a task should be executed, use Promise.all
.
It uses a list of Promises and returns a new single Promise.
import Promise from "apprt-core/Promise"
function waitForAll(promises){
return Promise.all(promises)
.then(results => {
// results is an array of the resolve arguments of the Promises in the list
let result1 = results[0];
});
}
In case of an error, the Promise.all
method uses a 'fail fast'' strategy,
which means that it reports the first error rejected by one of the Promises.
import Promise from "apprt-core/Promise"
function waitForAll(promises){
return Promise.all(promises)
.catch(error => {
//error is not an array!
//it is the first error rejected by one of the Promises
});
}
To trigger a task as soon as one Promise of a list of Promises is fulfilled, use Promise.race
.
It uses a list of Promises and returns a new single Promise.
import Promise from "apprt-core/Promise"
function waitForFirst(promises){
return Promise.race(promises)
.then(result => {
//result is not an array!
//it is the first result resolved by one of the Promises
},error => {
//error is not an array!
//it is the first error rejected by one of the Promises
});
}
apprt-core/Promise
To use a Promise from an API with unknown Promise implementation, use the Promise.wrap
method.
It ensures that all functions of apprt-core/Promise
are available.
import Promise from "apprt-core/Promise"
function task(externalPromise){
// wrap Promise
let p = Promise.wrap(externalPromise);
// enable state tracking feature
p = p.trackState();
return p.finally(()=>{
if (p.isFulfilled()){
...
}
});
}
JavaScript Promises specified in ECMAScript 2015 do not support cancellation semantics. The specification for CancelTokens was dropped, which means there is no official specification for cancellation.
The concept of a CancelablePromise
is used by apprt-core/Promise
together with apprt-core/CancelablePromise
.
The following samples illustrate there use.
import CancelablePromise from "apprt-core/CancelablePromise"
function task(){
let promise = new CancelablePromise((resolve,reject,oncancel)=>{
oncancel(()=>{
// add clean up stuff, if promise is cancelled
});
});
// only the original promise instance provides this method.
let cancel = promise.cancel;
// now add sub tasks
promise.then(doStuff);
// later stop any task added to the promise.
cancel();
}
import CancelablePromise from "apprt-core/CancelablePromise"
function task(){
// does not have a "cancel" method
let requestPromise = fetch("http://...");
let cancelablePromise = CancelablePromise.cancelable(requestPromise);
// only the original promise instance provides this method.
let cancel = cancelablePromise.cancel;
// now add sub tasks
cancelablePromise.then(doStuff);
// later stop any task added to the promise.
cancel();
}
import Promise from "apprt-core/Promise"
import Cancel from "apprt-core/Cancel"
function task(){
let promise = new Promise((resolve,reject) => {
reject(new Cancel());
});
// true
promise.trackState().isCancelled();
}
import CancelablePromise from "apprt-core/CancelablePromise"
function task(){
let promise = new CancelablePromise((resolve,reject,oncancel)=>{
oncancel(()=>{
// do cleanup
// also a promise can returned here
});
});
// cancel the promise and wait for cleanup code to be finished
promise.cancel().then(()=>{
// clean up finished
});
}