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:

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.

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:

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
    });
}