At first sight Promises do not fit comfortably in the family of OSGi specifications. They are neither a service nor a utility for handling OSGi itself. Until the Promises, the OSGi tried to shy away of those kind of utilities. What changed?

Well, several services needed an asynchronous completion mechanism. That, we needed to return a value before we actually had the value. At the time, I just had returned from a sabbatical where I dis some serious struggling with Javascript. Since Javascript at the time had promises, I proposed to use their design as our guiding light.

Since there was nothing suitable around in Java at that time, we decided to specify an OSGi Promise utility. This utility is available as a separate library and has not coupling to other OSGi API. I.e. we heavily use it in bnd although bnd itself is (unfortunately) not OSGi based.

What is a Promise?

The goal of a promise is to reliably execute a number of steps asynchronously. Traditionally we’ve been doing this in Java with callbacks. However, anybody that tries to sequence a number of asynchronous steps quickly realizes how complex it is to do this reliable. As usually, errors tend to make our beautiful code quickly become spaghetti. For normal Java, exceptions (for some) relieved us from the hard to read source code that continuously checked return codes. Promises do a similar simplification for asynchronous code.

In the most basic form, a Promise is a mechanism to report the result of an operation, either a value or an exception. This result can come immediately or sometime in the future. To get access to the value or failure, you can subscribe with a callback on the different events that can occur. Each of these events is guaranteed to only occur once.

The structure of a Promise looks like:

image

Example

An example use:

 Promise<Integer>    promise = foo( 42 );
 promise.thenAccept( System.out::println );

The foo function in this example returns a Promise. We do not care what it really did, but since the return is a Promise we should not use the value but react to the arrival of the value. This might already have happened, or it might happen in the future. We should not care.

Therefore, to process the result, we add a callback with the thenAccept() method that is executed when the promised value is available. (Is, not becomes, it might already be there.) A Promise can either be resolved with a value (including null) or it can fail with an exception. We call a Promise done when it is either resolved or failed. Callbacks can be added before or after a Promise becomes done, the result is the same, there no race conditions.

If it is resolved, the value is then printed by the lambda we passed to the thenAccept() method. The contract states that this happens asynchronously, which means this might happen even before the thenAccept() method returned. On the other hand, it might happen somewhere in the far future. The hard part of Promises is that you keep wanting to call the getValue() method. Suppress that urge, the idea is that you chain the steps. We’ll talk about that later.

No Happy Endings

If a Promise fails, then in the previous example, the exception is probably logged somewhere. However, we do not react on the exception, which is clearly not a good idea. We can therefore use add another callback to handle the failure:

 promise.onFailure( System.err::println );

When I started with Promises in Javascript, so many years ago, I remember one of the hardest parts was to understand that you could add callbacks or resolve or fail in any order you want. Although Promises are not immutable they can only become done once. This implies that each callback can only be called once ever, under whatever circumstances. Although Promises are not immutable (they transfer from not done to done), they are almost immutable and share many characteristics.

Although the absolute timing could differ with calling callbacks in different orders, the result in the end will always be the same. When you get a Promise, do not care if it is done or not. That is, when you’re tempted to ask a Promise if it is done, then you almost invariable have not got the Promise pattern yet. In all cases, you should register a callback and the promises figures out when to call you.

I know, it feels scary in the beginning. Zen, delegate, resist the temptation to take control …

It is important to realize that a Promise is not a conduit. It is strictly a one time fire once and forget thing. That is, it is not like a listener that can send multiple events. Each callback on a Promise is called at most once.

Where do I use Promises?

Promises are used in places where you need to react to events that you cannot wait for. The events happen asynchronously but you need to process them as step of a larger sequence. They basically provide a mechanism to keep multi step sequences readable and manageable.

The bnd code base is a heavy user of Promises, mostly in the realm of repositories. For example, in the bnd Maven Repository, there is a call:

 Promise<File> get(Archive gav);

Most of the time the requested archive is already in the .m2 Maven local repository and then the Promise will already have been resolved. However, when the file need to be fetched from Maven Central then the Promise that we receive will not yet be resolved. For the responsiveness it is quite important that we then not wait, especially at startup. At that time, bnd iterates over all available resources in the repositories. Having to wait sequentially for all of them would take forever.

In the naive way bnd would iterate over the resources and download the files one by one. That means that each file is downloaded sequentially. However, by using the Promises, bnd starts all the downloads sequentially and then processes the results one by one. In the end it will block until all downloads are done but during this blocking all downloads are executed in parallel. This is a tremendous performance improvement.

Sequential vs Parallel

In general areas where you benefit if you can separate the start of an action with the processing of an action because they are then parallelized without any effort. Although this pattern can easily be followed with existing Java mechanisms, the advantage of the Promise is that is highly optimized to handle the issues that arise in this kind of reactive programming.

Creating a Promise

To create a Promise, you need a Deferred. A Deferred object is the remote controller of the Promise. It is used to decide when the Promise fails or is resolved, the Promise itself has no API for this. That is, the Deferred is a privileged object that is kept by the party that controls the step.

For example, the foo method could be implemented as follows:

Promise<Integer> foo(int n) {
    Deferred<Integer> deferred = new Deferred<>();

    if (n > 10) {
        deferred.resolve(n);
    } else {
        deferred.fail(new Exception("Requires more than 10"));
    }

    return deferred.getPromise();
}

The Promise Factory

I hesitated to introduce the Promise Factory here because it looks like a minor detail for larger systems. However, it does simplify most of the following examples, so bear with me.

Asynchronous programming requires access to an Executor. Although Executor Services are trivially to obtain from the Executors class, they require careful handling in anything but the most trivial system. Executor Services are shared resources. Many larger systems find out that there are hundreds of (frequently badly configured) Executor Services in the system. Although sharing is often a pain in the ass, Executor Services should really be shared or at least be managed.

This is why OSGi enRoute registered a shared Executor. Although this Executor can be used, its API is not well aligned with the Promises API. The OSGi 1.1 Promises library therefore has a Promise Factory that manages the background threads while providing a very convenient API for application that uses Promises.

The following component could provide you with a shared PromiseFactory:

@Component(service=PromiseFactory.class)
public PromiseFactoryService extends PromiseFactory {    
  public PromiseFactoryService() {
    super( null, null);
  }
}

Since there is no need to have OSGi around, you can also create a Promise Factory with:

PromiseFactory pf = new PromiseFactory(null,null);

This will create a PromiseFactory using the default executor and scheduler.

Refactoring Deferred Away

When we have a Promise Factory we can make the foo() method a bit more realistic and run it in the background.

Promise<Integer> foo( int n ) {
   return pf.submit( () -> {
     if ( n > 10)
         return n;
     else
         throw new Exception("Requires more than 10");
  });
}

The Promise Factory submit() method is extremely convenient to schedule a step in the background. Since it uses a Callable, the step can throw an exception that is then automatically used to fail the returned Promise.

To Cache, or not to Cache

A common error in software design is to create multiple paths to the same solution. An example of this is getting a cached value. Since a cache can have a requested value or must make a value, it could have two ways to obtain a result. One method to see if there already is a value and the other to create the value. This is not very good design and Promises make it easy to create a unified efficient interface to these type of problems.

Caching and similar scenarios in a concurrent environment are hard. Promises help a lot by making a lot of race conditions and hard concurrent issues go away. Having a single uniform way to call a cache or similar subsystems is extremely valuable to make the overall system more simple. Less is more in this case.

For this reason, you can create Promises that are already pre-resolved for performance. Behind the scenes these Promises are highly optimized to minimize their overhead. A similar method exists for a failed Promise. Treat Promises as very low cost rendez-vous mechanisms.

For example, the interface to a cache with remote files. If the file is already local then we can immediately return a resolved Promise. If the file is not present, we need to start a download. In both cases we an return a Promise and the caller is oblivious of what we do.

Promise<File> get( Archive gav ) {
    try {
        File f = store.getLocalFile( gav );
        if ( f.isFile() ) 
            return pf.resolved(f);
            
        return remote.get( gav );
    } catch( Throwable t) {
        return pf.failed( t );
    }
} 

Chaining

So far the Promises looks a bit like a Rolex, an interesting thing but way over priced. Rest assured, this was just the introduction. Promises start to shine when you need perform multiple steps.

The simplest step is a mapping. Remember the method on the Maven Repository:

 Promise<File> get(Archive gav);

However, in this particular use case bnd needs a Resource, not a file. There is a function in bnd that can turn a File into a Resource but clearly we should not execute it before we have the file. That is, we need to map the File when it arrives to a Resource.

This looks like:

 Resource parse( File f) { ... }
 
 repo
      .get(archive)
      .map( this::parse );
      .thenAccept( System.out::println );

The previous example shows two stages. The first stage is initiated with the get() method, which returns Promise p1. We then call map() on this Promise p1, which returns Promise p2. Notice that we have Promise p2 before the file has arrived.

When the downloader is finished it will resolve p1. This will then run the parse method and the result of this method is used to resolve p2. When p2 is resolved, it will call System.out.println with the resource as argument.

I.e. the action diagram is:

image

Exception Handling in Chains

Quite invisible but arguably one of the most powerful aspects of the previous example is that it easy to correctly handle exceptions.

First, realize that when we have the promise, the download might still be going on (potentially) and that the parse method is only a blink in the eye of the downloader. I.e. any exceptions are still to be thrown.

Clearly there are now multiple independent failures possible. The download can fail or the parse method can fail somewhere in the future. Are we now required to subscribe to these two failures differently? Nope!

The primary raison d’être for the Promise is that when you chain promises, you only have to handle exceptions on the last promise. It is part of the contract that the creator of the deferred either resolves or fails the promise. This is true for users of Promises, but it is also how it works internally. If a promise fails, all its chained promises will also fail with the same exception. That is, don’t worry about the intermediate stages, only handle the exception on the last stage.

For example:

 repo
      .get(archive)
      .map( this::parse );
      .thenAccept( System.out::println )
      .onFailure( System.err::println )

Note the similarity with exception handling. Ok, with Java exceptions we get nice syntactic sugar but the model is similar. Each stage processes the happy case. If there is no happy ending, then we can catch the failure on the last promise.

Merging Multiple Promises

There are many use cases where you need to aggregate the result of a number of promises. Since Promises are the callback mechanism, we can provide a higher level function that takes a collection of promises and returns a promise that will be resolved with the list of resolved values.

For example, in bnd we need to download all files in a repository. As discussed earlier, we initiate the download and then later process the promises when all downloads are started. This way, all downloads happen in parallel.

So how would that look like? We need a list to hold our Promises that we create for each download. (We also map it to a Resource.) After we initiated all the downloads we use the Promise Factory to turn the list of Promises into a single Promise. This is done with the all()method. The resolved value for this second Promise is a List<Resource> and it will fail if any of the download fails. If it fails, the exception provides us access to the result of all the downloads that succeeded and the exceptions of the ones that failed.

We can then use the second Promise to turn the resources into a repository.

List<Promise<File>>         promises = new ArrayList<>();

for ( Archive gav : gavs ) {
    Promise<Resource>   promise = repo
        .get( gav )
        .map( this::parse );
    promises.add(promise);
}

ResourceRepository r = pf
    .all( promises )
    .aggregate.map( ResourceRepository::new )
    .getValue();

Recovery

The previous example is quite optimistic, it assumes all the download end happy. Clearly, any robust code must be able to handle errors during downloading and parsing.

As usual, exception handling is messy. However, especially in the user interface, it is quite useful to see which resources failed the download (or parsing). We would therefore like to recover from a failure. For this reason, a Promise has a recover() function. If the Promise fails, the recover function can provide an alternative value. In the previous example, we like to return an informational Resource that can report to a user what went wrong during the download (or parsing). We can thus replace the body of the previous example with:

    Promise<Resource>   promise = repo
        .get( gav )
        .map( this::parse )
        .recover( failedPromise -> info( failedPromise.getFailure(), gav ));
    promises.add(promise);

Now, if the download or the parsing fails, the recover function is called. Therefore, the list if Promises will never have any failed Promises.

Fallback

The fallback() method is used, for example, when you need to search in multiple places and the first one should win. Any error in an earlier one should be ignored. For example, we need to search through 3 repos.

Promise<File> promise = repo1
    .get(gav)
    .fallback( repo2.get(gav) )
    .fallback( repo3.get(gav));

Clearly this is quite expensive since the action is already initiated for all repositories although the first one might win.

Timeouts

In many reactive scenarios there are timers that abort operations or require it to take different steps after a certain amount of time. You can therefore create a new Promise that is guaranteed to fail with a TimeoutException after a number of milliseconds.

This highlights an important design pattern for the Promises. The timeout was not added to the getValue() method because that method should in principle not be used in a reactive design because it blocks. It was also not added to the deferred because that would make the owner of the Promise deciding about the timeout while maybe there are multiple consumers of the promise that have different requirements regarding a timeout.

As often, the best solution is therefore composition. Instead of adding the functionality to an existing Promise, a Promise can return a new Promise that fails with a TimeoutException after the timeout. The original Promise remains available sans timeout.

For example:

Promise<File> promise = repo1
    .get(gav)
    .timeout( 50_000 );

Delays

One of the peskiest problems in large scale reactive systems is overload. In a traditional system, the processing rate is controlled by the workers. They only ‘ask’ for new work when they’re ready. However, in a reactive system this natural push back is not present. This requires that the workers can send information to the producers to limit their rate. However, in a distributed system communicating takes time. For this, and several other reasons it is very important to use delays in a multi-step sequence to allow the system to settle.

A delay feature is built in to the Promise with the delay function. Just like the timeout function, this is implemented with composition.

 Promise<Resource> promise = repo1
        .get(gav)
        .delay( 1_000 )
        .thenAccept( this::parse );

Some Hard Problems Made Easy

In the following sections a number of hard problems are discussed and shown how the complexity disappears with Promises.

The Nasty Window

We tend to speak of caches as if they have the content or not. In reality, however, there is a nasty window where we might have to do some work to do before we have the answer. In that nasty window, you must block any new requests until you’ve done the work. This is quite a hairy problem that has a surprisingly simple solution with Promises.

Instead of maintaining a Map of our cached Widgets, we maintain a Map of Promises. Since we now can postpone providing a result, we can create the result at our leisure. Since everybody gets the same promise from the call to get, everybody will resolve at the same time.

Lets look! This is all we need:

final Map<String,Promise<Widget>>     widgets = new ConcurrentHashMap<>();

Promise<Widget> get( String key ) {
    return widgets.computeIfAbsent( k -> pf.submit( () -> new Widget(k) );
}    

Calling Once, Calling Twice

A common approach to maintain Service Level Agreements is to call remote services multiple times on different machines. The trick then is to return the first arriving answer. We only want to fail the resulting promise when all failed. How could we set this up?

<T> Promise<T> first( Collection<Promise<T>> promises ) {
    class Finisher {
        final Deferred<T> deferred = pf.deferred();   
        int count = promises.size();
    
        synchronized void fail(Throwable t) {
            
            if ( deferred.getPromise().isDone())
                return;
                
            if ( --count == 0)
                deferred.fail(t);
        }
        
        synchronized void resolve(Object value) {
            if ( deferred.getPromise().isDone())
                return;
            deferred.resolve(t);
        }
    }
    Finisher finisher = new Finisher();
            
    for ( Promise<T> promise : promises ) {
        promise.onResolve( finisher::resolve );
        promise.onFailure( finisher::fail );
    }
}

Strategy for Exceptions

The best part for last … #not. The hardest part of software is really how to handle the numerous unhappy case that evolve when we’re just trying to do good.

I’ve argued in many places that exceptions should be treated as booleans. When you call a method then either the method fails because something happened outside its contract, or it succeeds. That is, a method provides is successful or it fails. The reason for the failure should be immaterial, a failure is a failure from the code’s perspective. The message from the exception is important for posterity but by my definition it is wrong to react to the type of the exception because the exception signals a violation of the contract. Once you interpret the exception it becomes part of the contract which was the one and only thing it tried to signal. You could recover from a failure by trying an alternative route but by definition you should not try to address the cause of the exception. If that could be done, then the thrower, who knows so much more about the problem, should have done that.

Although this is the philosophy from Bertrand Meyer, the inventor of Eiffel, it is not commonly accepted (nor its consequences). This means we often are confronted with APIs that have made the thrown exceptions part of the contract. The worst offender is the File Not Found Exception.

Ok, enough ranting back to the subject. How should exceptions be handled with Promises?

My advise is clearly to treat exceptions as failures of a contract. This means that you need to consider your contract and think what is the part that should be handled and what is the part outside the contract.

An example that illustrates the dilemmas is a device driver communicating with an external device. If a driver runs into a communication error, is this an exception or not?

After a lot of thinking my conclusion was that it is not. Clearly, in such an environment communication problems will happen, there is no uncertainty here. These communication problems might be problematic but the code cannot treat this as a failure of the software contract. Communication errors are an intrinsic part of the protocol. We therefore decided that the value used to resolve the Promise should not only reflect the happy case, it should also be able to indicate the unhappy cases. For example, the payload of the Promise in a Driver could be:

static class Response {
    Payload payload;
    Status   status;
}

Mixing the status code of a communication error with a Null Pointer Exception in a sub module tends to make systems way more complex than handling these communication errors explicitly. Yes, it is no fun to check status codes but the alternative is worse.

For example, one status could be the infamous ‘Not Found’ error. Clearly, there is no use in retrying a request when the resource could not be found. However, the status could also indicate that the device had a communication timeout. Retrying it might succeed. Treating both as exceptions, mixed in with an NPE, would make this already hard area so much harder.

Therefore, do not make exceptions part of your control flow.

Completable Futures

One of the first rules in OSGi is: ‘Thou shall not invent the wheel’. Still, one day (not so long ago) I stumbled to my surprise on the Java 8 Completable Future which performs a similar role as the Promise. I’ve no idea what happened. True, the Promises were designed in 2013 which was ahead of Java 8. However, we knew what was coming and I am absolutely sure we looked in the Java 8 information. I recall we found a horrible class with a zillion methods that looked like no competition.

So, should you just ignore what you just read and jump on the Completable Future? After all, that class is part of the JDK, a hard to beat advantage.

I guess you can guess my answer. :-) Although they are very similar the Promise Factory provides easier centralized control over the background threads. Completable Future uses static methods and uses global pools. In an OSGi environment the Promise Factory allows for much better control for this important aspect.

Combined wiht the fact that Java 8 significantly reduced compatibility issues that pestered this kind of different subsystems in the past, I’ll stick to the Promises. Most of the API of both uses Functional interfaces and the compiler tapers over the type differences. I also consider the API of the OSGi Promise a bit better and much better documented.

Conclusion

It is this 2018 summer 6 years ago that I was struggling with Angular’s promises. Feeling quite stupid that I just did not get it, hey, this was supposed to be for Javascript developers! As with many things, giving it some time made my brains transform so they became a very useful tool in my toolbox. However, I can understand your pain if you’re getting started.

Promises do take some getting used to since their action diagrams are really twisted. However, my experience is that they can turn really hard problems into quite readable steps without ignoring unhappy endings.

Especially note that you can quite easily compose higher level functions out of Promises. Next asynchronous problem you encounter, think Promises.

Enjoy!

Peter Kriens

@pkriens


Comments