[Java] I want to write asynchronous processing using Promise in Java-Trial of Promise-like grammar of JavaScript-

Overview

--In Java language, I thought, "I want to write asynchronous processing and parallel processing with ** JavaScript Promise-like ** </ font> grammar." --The source is in the repository below https://github.com/riversun/java-promise

Code example

How should I write the process "When ** asynchronous process 1 ** is finished, ** asynchronous process 2 ** is executed using the result" in Java?

--Solution 1: Solution in the Java 1.4 era: Do your best with the function of Thread class --Holding threads in a nested manner, or waiting for the end with join. The dawn of parallel processing. --Solution 2: Solution in the Java5 (1.5) era: I wonder if I was happy with Callable / Future ... ――I'm happy to be able to return the results with Future / Callable, and I have all the props such as semaphores and latches, but I need to do my best. --Solution 3: Solution in the Java 8 era: You should be happy with Completable Future. ――Finally the long-awaited Future / Promise pattern mechanism is now available as standard!

――In this article, I wrote the following code in a JavaScript Promise-like way from a different perspective than the above three solutions.

Promise sample in Java


Promise.resolve()
 .then((action, data) -> {
   //Asynchronous processing 1
   new Thread(() -> {System.out.println("Process1");action.resolve("Result-1");}).start();
 })
 .then((action, data) -> {
   //Asynchronous processing 2
   new Thread(() -> {System.out.println("Process2");action.resolve("Result-2");}).start();
 })
 .start();

What I want to do in this article

--What you want to do is ** "Write in JavaScript Promise-like" ** </ font> --A series of processing that calls multiple APIs asynchronously and calls the next API when the result is received. --Processing that moves multiple processes simultaneously (in parallel) and moves on to the next process when all of them are completed.

Not covered in this article

-Realization of (academic) Future / Promise patterns --How to use Java standard concurrent processing --ExecutorService and Callable that can be used from Java5 (1.5.0) or later //docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Callable.html) --How to write using CompletableFuture that can be used from Java 8 or later

Target environment

--Java 5 or later --The library also works on Java 1.6 based Android --Java 8 concurrent API is not used

How to use (dependency)

It is in the Maven repository as a library ** java-promise **, so you can use it immediately by adding the following.

Maven

POM.xml dependency


<dependency>
	<groupId>org.riversun</groupId>
	<artifactId>java-promise</artifactId>
	<version>1.1.0</version>
</dependency>

Gradle

build.gradle


dependencies {
    compile 'org.riversun:java-promise:1.1.0'
}

build.gradle(Android)


dependencies {
    implementation 'org.riversun:java-promise:1.1.0'
}

Main story

Comparison between Promise written in JavaScript and the method written in Java introduced in this article

** First, write a promise in JavaScript for comparison **

The following code is a JavaScript sample that just concatenates the asynchronously executed processing result ('bar') to the string'foo'. Web / JavaScript / Reference / Global_Objects / Promise / then # Chaining) code. This is an excerpt from the one published as a Promise sample in MDN.

Example.js


Promise.resolve('foo')
    .then(function (data) {
        return new Promise(function (resolve, reject) {
            setTimeout(function () {
                const newData = data + 'bar';
                resolve(newData);
            }, 1);
        });
    })
    .then(function (data) {
        return new Promise(function (resolve, reject) {
            console.log(data);
            resolve();
        });
    });
console.log("Promise in JavaScript");

The execution result is as follows

Promise in JavaScript
foobar

** Then write using java-promise in Java 8 **

Example.java


import org.riversun.promise.Promise;

public class Example {

    public static void main(String[] args) {

        Promise.resolve("foo")
                .then(new Promise((action, data) -> {
                    new Thread(() -> {
                        String newData = data + "bar";
                        action.resolve(newData);//#Move to the next process with resolve
                    }).start();//Run in a separate thread
                }))
                .then(new Promise((action, data) -> {
                    System.out.println(data);
                    action.resolve();
                }))
                .start();//Trigger to start processing
        System.out.println("Promise in Java");
    }
}

The execution result is as follows

Promise in Java
foobar

Since the execution below the Promise is performed asynchronously (separate thread), you can see that System.out.println ("Promise in Java "); is executed in this example.

For convenience of processing I tried to get closer to JavaScript's Promise-like grammar except that it called .start () at the end to trigger the Promise chain.

notation

Write without lambda expressions (Java 7 or earlier)

Without using a lambda expression:

When written without using a lambda expression


Promise.resolve("foo")
        .then(new Promise(new Func() {
            @Override
            public void run(Action action, Object data) throws Exception {
                new Thread(() -> {
                    String newData = data + "bar";
                    action.resolve(newData);
                }).start();
            }
        }))
        .then(new Promise(new Func() {
            @Override
            public void run(Action action, Object data) throws Exception {
                new Thread(() -> {
                    System.out.println(data);
                    action.resolve();
                }).start();
            }
        }))
        .start();

The identity of the part where (action, data)-> {} is is the interface that represents function in JavaScript.

Func.java


public interface Func {
    public void run(Action action, Object data) throws Exception;
}

Write more simply

You can use Promise.then (new Func ()) instead of Promise.then (new Promise ()). If you replace new Func with a lambda expression, it becomesPromise.then ((action, data)-> {}), which makes it even simpler.

then(Func)Write using


Promise.resolve("foo")
   .then((action, data) -> {
       new Thread(() -> {
           String newData = data + "bar";
           action.resolve(newData);
       }).start();
   })
   .then((action, data) -> {
       System.out.println(data);
       action.resolve();
   })
   .start();

Introduction of various patterns of parallel execution using Promise

(1) Promise.then: </ font> Execute asynchronous processing in order

code:

public class Example20 {

    public static void main(String[] args) {

        //Process 1 (execution of another thread)
        Func function1 = (action, data) -> {
            new Thread(() -> {
                System.out.println("Process-1");
                Promise.sleep(1000);// Thread.Same as sleep
                action.resolve("Result-1");//Status"fulfilled"And the result in the next process("Result-1")Tell
            }).start();//Start asynchronous processing in another thread
        };

        //Process 2
        Func function2 = (action, data) -> {
            System.out.println("Process-2 result=" + data);
            action.resolve();
        };

        Promise.resolve()//Start processing
                .then(function1)//Process 1 execution
                .then(function2)//Process 2 execution
                .start();//start

        System.out.println("Hello,Promise");
    }

Execution result:

Hello,Promise
Process-1
Process-2 result=Result-1

Description: The grammar of ** then ** is ** Promise.then (onFulfilled [, onRejected]); ** That is, it can take up to two arguments. The first argument ** onFulfilled ** is executed if the previous execution ended with fulfilled (≒ successful) </ font> status. The second argument ** onRejected ** is optional, but it will be executed if the previous execution ended with rejected (≒ failed) </ font> status. This sample specifies only the first argument.

** Processing flow: **

image.png

  1. Set the status to fullfilled </ font> with ** Promise.resolve ** and chain to ** then **.
  2. Since fullfilled </ font>, ** then ** executes ** function1 ** specified in the first argument.
  3. ** function1 ** also changes the status to fullfilled </ font> by ** action.resolve **
  4. ** function1 ** sets ** action.resolve ** to String type argument ** "Result-1" **
  5. The next ** then ** also has a status of fullfilled </ font>, so ** function2 ** is executed
  6. ** function2 ** Runtime argument ** data ** contains the result ** "Result-1" ** of ** function1 **

(2) action.resolve, action.reject: </ font> Branch the process depending on the execution result

code:

public class Example21 {

    public static void main(String[] args) {

        Func function1 = (action, data) -> {
            System.out.println("Process-1");
            action.reject();//Status"rejected"Set to and complete execution
        };

        Func function2_1 = (action, data) -> {
            System.out.println("Resolved Process-2");
            action.resolve();
        };

        Func function2_2 = (action, data) -> {
            System.out.println("Rejected Process-2");
            action.resolve();
        };

        Promise.resolve()
                .then(function1)
                .then(
                        function2_1, //Executed when the status is fulfilled
                        function2_2 //Executed when the status is rejected
                )
                .start();

        System.out.println("Hello,Promise");

    }
}

Execution result:

Hello,Promise
Process-1
Rejected Process-2

Description:

** function1 ** is

action.reject();

Since it is completed with, the status will be rejected </ font>. The next ** then ** is

 .then(
         function2_1, //Executed when the status is fulfilled
         function2_2 //Executed when the status is rejected
 )

It is said. As mentioned above, the grammar of ** then ** is ** Promise.then (onFulfilled [, onRejected]); **, so Since the completion status of ** function1 ** is rejected </ font>, ** function2_2 **, which is the second argument of ** then **, is executed here.

** Processing flow: **

image.png

(3) ** Promise.always: ** </ font> Receive both resolve and reject processing results

code:

public class Example30 {

    public static void main(String[] args) {
        Func function1 = (action, data) -> {
            action.reject("I send REJECT");
        };
        Func function2 = (action, data) -> {
            System.out.println("Received:" + data);
            action.resolve();
        };
        Promise.resolve()
                .then(function1)
                .always(function2)//The status is"fulfilled"But"rejected"But実行される
                .start();
    }
}

Execution result:

Received:I send REJECT

Description:

.always(function2)

** always ((action, data)-> {}) ** has a status <font color due to rejected, even if the previous process had a status fulfilled </ font> due to resolved. Always executed regardless of = red> rejected </ font>.

** Processing flow: **

image.png

(4) ** Promise.all **: </ font> Wait for the completion of multiple parallel asynchronous processes and proceed to the next

code:

public class Example40 {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        //Asynchronous processing 1
        Func function1 = (action, data) -> {
            new Thread(() -> {
                Promise.sleep(1000); System.out.println("func1 running");action.resolve("func1-result");
            }).start();
        };
        //Asynchronous processing 2
        Func function2 = (action, data) -> {
            new Thread(() -> {
            Promise.sleep(500);System.out.println("func2 running"); action.resolve("func2-result");
            }).start();
        };
        //Asynchronous processing 3
        Func function3 = (action, data) -> {
            new Thread(() -> {
            Promise.sleep(100);System.out.println("func3 running");action.resolve("func3-result");
            }).start();
        };
        //The process of finally receiving the result
        Func function4 = (action, data) -> {
            System.out.println("Received the result");
            List<Object> resultList = (List<Object>) data;
            for (int i = 0; i < resultList.size(); i++) {
                Object result = resultList.get(i);
                System.out.println("Asynchronous processing" + (i + 1) + "The result of" + result);
            }
            action.resolve();
        };
        
        Promise.all(function1, function2, function3)
                .always(function4)
                .start();
    }
}

Execution result:

func3 running
func2 running
func1 running
Received the result of asynchronous processing
The result of asynchronous process 1 is func1-result
The result of asynchronous processing 2 is func2-result
The result of asynchronous process 3 is func3-result

Description:

-** Promise.all (function1, function2, ... functionN) ** can take multiple processes of ** function1 ** to ** functionN ** as arguments and execute them in parallel.

--When parallel execution is completed, the process shifts to the chained ** then ** (here ** always **). --In the above example, ** function1, function2, function3 ** are executed in parallel, but if ** function1 to function3 ** are all completed with fulfilled </ font>, each * The results of * function1 to function3 ** are stored in ** List ** and passed to ** then **. At that time, the storage order is the order of ** function1, function2, function3 ** specified in the argument. (This specification is also the same as the JavaScript Promise)

-** If any one of function1 to function3 ** fails ≒ reject </ font>, it will be reject </ font> first. The result (reject reason) of the function is passed to the next ** then **. (Fail-fast principle)

** Processing flow: **

image.png

(5) ** Promise.all **: Part 2 </ font> Specify the thread pool yourself

As explained in (4), ** Func ** can be operated in parallel with ** Promise.all **, but ** Executor ** is used as the thread creation policy when performing parallel operation in advance. Can be defined. In addition, the thread pool already prepared for another purpose may be diverted to ** Promise.all **.

** Code example: **

public class Example41 {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {

        final ExecutorService myExecutor = Executors.newFixedThreadPool(2);

        //Asynchronous processing 1
        Func function1 = (action, data) -> {
            System.out.println("No.1 " + Thread.currentThread());
            new Thread(() -> {
                Promise.sleep(1000);System.out.println("func1 running");action.resolve("func1-result");
            }).start();
        };

        //Asynchronous processing 2
        Func function2 = (action, data) -> {
            System.out.println("No.2 " + Thread.currentThread());
            new Thread(() -> {
                Promise.sleep(500);System.out.println("func2 running");action.resolve("func2-result");
            }).start();
        };

        //Asynchronous processing 3
        Func function3 = (action, data) -> {
            System.out.println("No.3 " + Thread.currentThread());
            new Thread(() -> {
                Promise.sleep(100);System.out.println("func3 running");action.resolve("func3-result");
            }).start();
        };
        
        //The process of finally receiving the result
        Func function4 = (action, data) -> {
            System.out.println("No.4 final " + Thread.currentThread());
            System.out.println("Received the result");
            List<Object> resultList = (List<Object>) data;
            for (int i = 0; i < resultList.size(); i++) {
                Object result = resultList.get(i);
                System.out.println("Asynchronous processing" + (i + 1) + "The result of" + result);
            }
            myExecutor.shutdown();
            action.resolve();
        };

        Promise.all(myExecutor, function1, function2, function3)
                .always(function4)
                .start();
    }
}

Execution result:

No.1 Thread[pool-1-thread-2,5,main]
No.2 Thread[pool-1-thread-2,5,main]
No.3 Thread[pool-1-thread-2,5,main]
func3 running
func2 running
func1 running
No.4 final Thread[pool-1-thread-1,5,main]
Received the result
The result of asynchronous process 1 is func1-result
The result of asynchronous processing 2 is func2-result
The result of asynchronous process 3 is func3-result

From the results, it can be seen that ** Func ** is running on threads taken from the same thread pool. (Since the asynchronous processing (new Thread) is intentionally performed in Func, the asynchronous processing is outside the specified thread pool.)

Description:

-Define the ** Executor ** used to execute ** Promise.all **. Below is a thread pool with a pool size of 2.

final ExecutorService myExecutor = Executors.newFixedThreadPool(2);

-** Executor ** can be specified like Promise.all (executor, func1, func2, func3, ... funcN) **

 Promise.all(myExecutor, function1, function2, function3)
         .always(function4)
         .start();

--If you specify ** Executor ** yourself, don't forget to ** shutdown **

Func function4 = (action, data) -> {
   //Omission
    myExecutor.shutdown();
    action.resolve();
};

** Thread spawning policy: **

- Thread pool size must be ** 2 ** or larger. </ font> (That is, singleThreadExecutor is not available.) -In ** java-promise **, when performing ** Promise.all **, one thread is used for asynchronous execution. --Furthermore, since parallel execution is performed by ** Promise.all **, at least one thread is required for parallel execution. (Although one thread is not called parallel) --The total of these two requires 2 or more threads.

Summary

--I tried the method of "writing Promise like JavaScript" in Java --If you take Java8 lambda expression well, you can execute Promise in a form close to JavaScript notation. ――I would like to learn from the evolution of JavaScript (ES) and other scripting languages that you can perform smart processing with simple notation. (Asynchronous execution has also evolved to ** async / await ** in JavaScript) --The library side source code of Promise (** java-promise **) in Java is below https://github.com/riversun/java-promise --git clone https://github.com/riversun/java-promise.git --mvn test will give you a unit test.

--In addition, the sample code posted in this article is below. https://github.com/riversun/java-promise-examples/tree/master-ja

Recommended Posts