Asynchronous Java processing with promise

The Reactive Ťoolbox Core library implements Promise-based asynchronous processing model somewhat similar (but not identical) to one implemented by ECMAScript 2015.

Probably main difference is that Promise implemented by the library has only two states (pending and resolved) as opposed to three states in ECMAScript (pending, resolved and rejected). This difference is caused by the need for ECMASCript to implement error handling (hence third rejected state), while Reactive Toolbox Core uses Result<T>-based error handling model and does not need separate state for error.

The need to support only two states greatly simplifies the implementation. The promise of the implementation in the library is much simpler than, for example, much lighter in the future. This method can also use fluent and transparent syntax to easily handle complex interactive solutions.

Let's study this implementation in more depth.

Commitments can be created in pending and resolved states:

final var pendigPromise = Promise.<Integer>promise();
final var resolvedPromise = Promise.<Integer>fulfilled(123);

Only the first resolution is accepted and used as the promise example result. All subsequent attempts to resolve the promise will be ignored.

The user can ensure additional actions. Once the promise is fulfilled, these actions will be performed:

final var holder = new AtomicInteger(-1);
final var promise = Promise.<Integer>promise()
                              .onSuccess(holder::set);

If possible, the instance of promise.ok (1) will be resolved and additional operations will be performed. The additional action is only performed once. If the instance is resolved when a new operation is attached, the operation is executed immediately in the context of the calling thread. If the instance has not been resolved, additional operations will be performed in the thread context of the promise that has been resolved.

Promise has a built-in scheduler that can be adjusted for a large number of non-blocking small and short-term tasks. This scheduler can be used to perform certain operations on promises asynchronously, to parse promises asynchronously, or to schedule operations on promises after the specified timeout period expires. The overhead of the timeout implementation is very small and there are no other threads.

More examples:

// create instance and immediately invoke code which uses this instance
final var promise = Promise.<JWT>promise(p -> p.resolve(service.createNewToken(user)))

// Set promise processing timeout
final var promise = Promise.<User>promise()
                               .when(Timeout.timeout(30).seconds(), Errors.TIMEOUT::asFailure);

// Resolve promise asynchronously, so all attached actions will be run in other thread
promise.asyncResolve(Result.success("ok"));

Sometimes, it is necessary to convert the received results before further delivery. Promise maps for this purpose ():

final Promise<String> promise = service.promiseRandomInteger()
                                       .map(Objects::toString);

Now it's worth looking at more complicated scenarios.

Promise.any() and Promise.anySuccess()

Sometimes it is necessary to receive at least one response from a different service. If there is any response, the static Promise.any () can be used:

final Promise<Integer> promise = Promise.any(service1.promiseInteger(),
                                             service2.promiseInteger());

Regardless of whether it is a success or failure, the promise resolved first will be returned as the result. But in most cases, only successful results are meaningful, then the following methods will help:

final Promise<Integer> promise = 
         Promise.anySuccess(service1.promiseInteger(),
                            service2.promiseInteger());

In this case, only when all commitments are resolved as failures can commitments be resolved as failures.

Another important feature of this method is that once it succeeds, it will cancel (ie resolve special types of failures) all remaining outstanding commitments.

Promise.all()

Quite often it is necessary to obtain several results before doing further processing. The set of static Promise.all() method serve exactly this purpose - resulting promise is resolved to success only when all passed promises are resolved to success. Successful result in this case returned as Tuple:

   final Promise<Tuple3<Integer, String, UUID>> promise = 
            Promise.all(integerService.promiseInteger(),
                        stringService.promiseString(),
                        uuidService.promiseUuid());

Sometimes it is necessary to get some intermediate values ​​and then pass them to another service, which will calculate the result. In other words, this is the case where the call returns a promise. To this end, the library provides special methods:

Promise.chainMap()

The following example illustrates the usage of this method:

    private ArticleService articleService;
    private TopicService topicService;
    private UserService userService;

    public Promise<List<Article>> userFeedHandler(final User.Id userId) {
        return all(topicService.topicsByUser(userId, Order.ANY),
                   userService.followers(userId))
                .chainMap(tuple -> tuple.map((topics, users) -> articleService.userFeed(map(topics, Topic::id), map(users, User::id))))
                .when(timeout(30).seconds(), failure(Errors.TIMEOUT));
    }

Let us study this example in more depth. First, Promise.all () gets the results from topicService and userService. When these services successfully fulfill their promises, the articleService is invoked using the converted values ​​received from the first two invocations. Finally, a timeout is configured for the returned commitment, so even if there are any problems, the final commitment will be resolved due to a timeout error.

All these complex processes and all related error handling are just one line of code.

Summary

The promise of asynchronous processing combined with other functional programming techniques is a very powerful and expressive tool. It can easily express complex processing procedures while keeping the code clean and easy to read and write. The overall application of the FP style on Java makes it easy to create modern applications that are easy to create, extend, test, and maintain.

Afterword

The Reactive Ťoolbox Core is still work in progress. It requires a lot of effort and I realize that the whole project is way bigger than one person can accomplish in reasonable time. So, those who want to participate are welcome to join project. Comments, ideas, suggestions are welcome as well.

from: https://dev.to//siy/asynchronous-processing-in-java-with-promises-2oj0

Published 0 original articles · liked 0 · visits 413

Guess you like

Origin blog.csdn.net/cunxiedian8614/article/details/105690051