Extended Promise Support
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, neitherfulfilled
norrejected
.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.
Create a Promise
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
.
- The
resolve
function is used to mark the Promise as successfully fulfilled. - The
reject
function is used to mark the Promise as rejected by an error and should be called with an Error instance as parameter.
Append a 'success' handler to a Promise
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.
Append an 'error' handler to a Promise
Use 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.
Use 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.
Use 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.
Append a 'finally' handler to a Promise
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.
Chaining of handler functions
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);
}
Switch between 'fulfilled' and 'rejected' states in the handler chain
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.
A handler can return another Promise to delay the following 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.
Create Promises for 'success' or 'error' results
To create Promise instances directly, because a result is already available but the API should be asynchronous, see the following samples.
Create 'fulfilled' Promise by Promise.resolve
import Promise from "apprt-core/Promise"
function retrieveMessage(){
let msg = "a message";
return Promise.resolve(msg);
}
Create 'rejected' Promise by Promise.reject
import Promise from "apprt-core/Promise"
function produceWrongParameterError(){
return Promise.reject(new Error("wrong parameter"));
}
Track Promise states
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 likedojo/Deferred.isFulfilled
, which hasisSettled
semantics)isRejected
: true, if Promise is rejectedisCancelled
: true, if Promise is rejected by aapprt-core/Cancel
instance
Sample:
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
}
});
}
Special Promise helper methods
Wait for multiple Promises to be finished with Promise.all
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
});
}
Wait for the fastest Promise of multiple Promises to be finished with Promise.race
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
});
}
Ensure that a Promise is a 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()){
...
}
});
}
Cancellation
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.
Create a CancelablePromise
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();
}
Use CancelablePromise.cancelable to wrap an existing promise
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();
}
Use Cancel to trigger cancellation event from within a Promise
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();
}
Wait as canceler for cancel to be finished
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
});
}