Git Product home page Git Product logo

jasync's Introduction

JAsync - the async-await pattern of Java

Maven Release

中文版

JAsync implements Async-Await pattern just like es in Java. It allows developers to write asynchronous code in a sequential fashion. It makes the developer's asynchronous programming experience as close as possible to the usual synchronous programming, including code style and debugging.

On the other hand, this framework separates the realization of the upper-level syntax tree conversion and the lower-level asynchronous workflow through a set of interfaces. This project focuses on the former, while the latter can be achieved by encapsulating existing asynchronous libraries, such as Reactor, RxJava, etc. The advantage of this is that this project can be seamlessly integrated with various asynchronous libraries that have been widely used in the community to produce an effect of 1+1>2.

Requirement

jdk >= 8

Examples

With JAsync

@RestController
@RequestMapping("/employees")
public class MyRestController {
    @Inject
    private EmployeeRepository employeeRepository;
    @Inject
    private SalaryRepository salaryRepository;

    // The standard JAsync async method must be annotated with the Async annotation, and return a JPromise object.
    @Async()
    private JPromise<Double> _getEmployeeTotalSalaryByDepartment(String department) {
        double money = 0.0;
        // A Mono object can be transformed to the JPromise object. So we get a Mono object first.
        Mono<List<Employee>> empsMono = employeeRepository.findEmployeeByDepartment(department);
        // Transformed the Mono object to the JPromise object.
        JPromise<List<Employee>> empsPromise = Promises.from(empsMono);
        // Use await just like es and c# to get the value of the JPromise without blocking the current thread.
        for (Employee employee : empsPromise.await()) {
            // The method findSalaryByEmployee also return a Mono object. We transform it to the JPromise just like above. And then await to get the result.
            Salary salary = Promises.from(salaryRepository.findSalaryByEmployee(employee.id)).await();
            money += salary.total;
        }
        // The async method must return a JPromise object, so we use just method to wrap the result to a JPromise.
        return JAsync.just(money);
    }

    // This is a normal webflux method.
    @GetMapping("/{department}/salary")
    public Mono<Double> getEmployeeTotalSalaryByDepartment(@PathVariable String department) { 
        // Use unwrap method to transform the JPromise object back to the Mono object.
        return _getEmployeeTotalSalaryByDepartment(department).unwrap(Mono.class);
    }
}

In this example, JAsync rewrite the code like XXX.await() to XXX.thenVoid(v -> { ... }) to making your methods non-blocking. With JAsync, you can not only enjoy the high throughput of non-blocking programming, but also avoid callback hell and counter-intuitive chained function calls.

What JAsync do.

@RestController
@RequestMapping("/employees")
public class MyRestController {
    @Inject
    private EmployeeRepository employeeRepository;
    @Inject
    private SalaryRepository salaryRepository;

    @Async()
    private JPromise<Double> _getEmployeeTotalSalaryByDepartment(String department) {
        double money = 0.0;
        DoubleReference moneyRef = new DoubleReference(money);
        Mono<List<Employee>> empsMono = employeeRepository.findEmployeeByDepartment(department);
        JPromise<List<Employee>> empsPromise = Promises.from(empsMono);
        return empsPromise.thenVoid(v0 -> JAsync.doForEachObject(v0, employee ->
                Promises.from(salaryRepository.findSalaryByEmployee(employee.id)).thenVoid(v1 -> {
                    moneyRef.addAndGet(v1.total);
                })
            ).thenVoid(() -> JAsync.doReturn(JAsync.just(moneyRef.getValue())))).catchReturn();
    }

    // This is a normal webflux method.
    @GetMapping("/{department}/salary")
    public Mono<Double> getEmployeeTotalSalaryByDepartment(@PathVariable String department) { 
        // Use unwrap method to transform the JPromise object back to the Mono object.
        return _getEmployeeTotalSalaryByDepartment(department).unwrap(Mono.class);
    }
}

How to use?

First, select a implementation library to the Maven dependency. Currently, only one implementation is available.

<dependency>
    <groupId>io.github.vipcxj</groupId>
    <artifactId>jasync-reactive</artifactId>
    <version>0.1.9</version>
</dependency>

This implementation uses the famous library Reactor. The JPromise object is a wrapper of Mono object. So the JPromise object can be created from a Mono object using static method io.github.vipcxj.jasync.reactive.Promises.from(reactor.core.publisher.Mono<T>). And the JPromise object can be converted back to the Mono object using instance method JPromise.unwrap.

Then add the core library to the Maven dependency.

<dependency>
    <groupId>io.github.vipcxj</groupId>
    <artifactId>jasync-core</artifactId>
    <version>0.1.9</version>
    <scope>provided</scope>
</dependency>

The core library is only need at compile stage, so here use the provided scope. Generally, the annotation processor should be discovered by jdk automatically. However, if something went wrong, jdk can not find the annotation processor, try this:

<plugins>
  <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
      <annotationProcessorPaths>
        <path>
          <groupId>io.github.vipcxj</groupId>
          <artifactId>jasync-core</artifactId>
          <version>0.1.9</version>
        </path>
      </annotationProcessorPaths>
    </configuration>
  </plugin>
</plugins>

Note: If there exists annotationProcessorPaths configure, the dependency configure will not work.

Debug mode

JAsync support a debug mode. With debug mode on, JAsync will inject all useful variable to the current context, even they are not captured. As a result, When debugging, the developer can see all the variables in the monitor window just like debugging normal code.

For example, with debug mode off:

alt debug mode off

With debug mode on:

alt debug mode on

It can be seen that when the debug mode is turned on, all the defined variables can be found in the monitoring window.

About Lombok

If you are using Lombok, you should place the JAsync after the Lombok, just like this:

<plugins>
  <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
      <annotationProcessorPaths>
         <path>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
         </path>
        <path>
          <groupId>io.github.vipcxj</groupId>
          <artifactId>jasync-core</artifactId>
          <version>0.1.9</version>
        </path>
      </annotationProcessorPaths>
    </configuration>
  </plugin>
</plugins>

Known Issues

  1. Not support new switch syntax introduced in java 17. It will be supported in the near future.
  2. Not support ejc (eclipse java compiler). I will try my best to support it. Currently, you can compile the project using maven or gradle, then debug using ejc.

jasync's People

Contributors

vipcxj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

jasync's Issues

compile fail when using generics params

image

raw code

@Async(logResultTree = true)
public <G> JPromise<G> get(Class<G> format) {
    Long data = JAsync.just(1l).await();
    return JAsync.just(format.cast(data));
}

compiled code

@Async(logResultTree = true)
public <G> JPromise<Void> get(Class<G> format) {
  return JAsync
    .just(1L)
    .thenVoid(
      indyHelper$$0.voidPromiseFunction(
        "get$$tmp$$1",
        java.lang.invoke.MethodType.methodType(
          io.github.vipcxj.jasync.spec.JPromise.class,
          java.lang.Class.class,
          java.lang.Long.class
        ),
        format
      )
    )
    .catchReturn();
}

private io.github.vipcxj.jasync.spec.JPromise<java.lang.Void> get$$tmp$$1(
  final java.lang.Class<G> format,
  final java.lang.Long tmp$$0
)
  throws java.lang.Throwable {
  Long data = tmp$$0;
  return io.github.vipcxj.jasync.spec.JAsync.doReturn(
    JAsync.just(format.cast(data))
  );
}

private static final io.github.vipcxj.jasync.runtime.java8.helpers.IndyHelpers indyHelpers$$0 = new io.github.vipcxj.jasync.runtime.java8.helpers.IndyHelpers(
  java.lang.invoke.MethodHandles.lookup()
);
private final io.github.vipcxj.jasync.runtime.java8.helpers.IndyHelper indyHelper$$0 = new io.github.vipcxj.jasync.runtime.java8.helpers.IndyHelper(
  indyHelpers$$0,
  this
);

loss the generics type in sub function
image

Cannot load provider in a servlet setup with spring

Experience an error No provider of PromiseProvider found! when running servlet application with spring.

I guess it may be related to the class loader hierarchy or class loading sequence.

Currently I hack around this by adding a listener to the servlet

	<listener>
		<listener-class>ServletStartupListener</listener-class>
	</listener>
public class ServletStartupListener implements ServletContextListener {
	
	public void contextInitialized(ServletContextEvent sce) {
		log.info("Init JAsync");
		JAsync.just();
	}
	
}

I think one approach to solve this issue is to lazy load the provider instead of loading it in the static phase at JAsync.class

ContinueException in 【for...continue...】

  @Async(logResultTree = true)
  public JPromise<Void> get() {
      ArrayList<Long> list = new ArrayList<>() {
          {
              add(1l);
              add(2l);
          }
      };

      for (Long l : list) {
          if (true) continue;   // throw io.github.vipcxj.jasync.spec.ContinueException
          JAsync.just().await();
      }

      return JAsync.just();
  }

but the following example is successful

  @Async(logResultTree = true)
  public JPromise<Void> get() {
      ArrayList<Long> list = new ArrayList<>() {
          {
              add(1l);
              add(2l);
          }
      };

      for (Long l : list) {
          JAsync.just().await();
          if (true) continue;
      }

      return JAsync.just();
  }

Micronaut + Reactor + JAsync doesn't compile

Hi,

I tried to implement my first demo project with micronaut/reactor. Unfortunately this error pops up during compilation:

Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project test: Fatal error compiling: java.lang.IllegalAccessError: class io.github.vipcxj.jasync.core.javac.JAsyncContext (in unnamed module @0x42ed89da) cannot access class com.sun.tools.javac.api.JavacTrees (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.api to unnamed module @0x42ed89da -> [Help 1]

This is the sample project: https://github.com/mojo2012/async-test

I hope you can help me fix this :-)

cheers, matthias

[Opinion solicitation] How should the api converting from third part object to Promise look like?

The implementation module which wrap the third part library such as Reactor or JxJava should be completely isolated from the core module. In other words, the implementation module should depend on core module, but the core module should not depend on the implementation module. This cause a problem, I don't know how should the api converting from third part object to Promise look like.

  • a: Use static method <T> Promise<T> JAsync.from(Object) in the core module. Obviously this is ugly and error-prone. But the fact that apis are aggregated together is an advantage.
  • b: Use static method <T> Promise<T> Promises.from(XXX<T> from) in the implementation module, where XXX may be Mono or Single or Future. This API makes perfect use of generics and looks more reliable but it doesn't make for a unified API.

Compilation error of for loop

Unable to handle an empty for loop

Java Code

@Async
private JPromise<Boolean> method1(){
	for(;;){
		// do something
	}
	.....
}

Build Error

[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] SomeFile.java:[692] reference to doFor is ambiguous
  both method doFor(io.github.vipcxj.jasync.spec.functional.VoidPromiseSupplier,io.github.vipcxj.jasync.spec.functional.BooleanSupplier,io.github.vipcxj.jasync.spec.functional.VoidPromiseSupplier,io.github.vipcxj.jasync.spec.functional.VoidPromiseSupplier,java.lang.String) in io.github.vipcxj.jasync.spec.JAsync and method doFor(io.github.vipcxj.jasync.spec.functional.VoidPromiseSupplier,io.github.vipcxj.jasync.spec.functional.PromiseSupplier<java.lang.Boolean>,io.github.vipcxj.jasync.spec.functional.VoidPromiseSupplier,io.github.vipcxj.jasync.spec.functional.VoidPromiseSupplier,java.lang.String) in io.github.vipcxj.jasync.spec.JAsync match

compile error

@Async(logResultTree = true)
    public JPromise<Void> test() {
        String a = "定义a";

        try {
            a = "修改a";
            for (Long l : getList(a)) {
                JAsync.just().await();
            }
        }
        catch(Exception error) {

        }

        return JAsync.just();
    }

    private ArrayList<Long> getList(String a) {
        return new ArrayList<Long>();
    }

compile error

    @Async(logResultTree = true)
    private static JPromise<Void> test(String command) {
        try {
            command = "对输入参数赋值,就挂了";
            JAsync.just().await();
        }
        catch( Exception e) {

        }
        return JAsync.just();
    }

compile fail when function params be used in lambda

raw code

  @Async(logResultTree = true)
  public <G> JPromise<Object> get(Object format) {
      JAsync.just().await();
      return JAsync.just(Optional.ofNullable(null).map(l -> format).get());
  }

compiled code

@Async(logResultTree = true)
public <G>JPromise<Object> get(Object format) {
    return JAsync.just().thenVoid(indyHelper$$0.voidPromiseFunction("get$$tmp$$1", java.lang.invoke.MethodType.methodType(io.github.vipcxj.jasync.spec.JPromise.class, java.lang.Object.class))).catchReturn();
}

private <G>io.github.vipcxj.jasync.spec.JPromise<java.lang.Void> get$$tmp$$1(final java.lang.Object tmp$$0) throws java.lang.Throwable {
    ;
    return io.github.vipcxj.jasync.spec.JAsync.doReturn(JAsync.just(Optional.ofNullable(null).map((l)->format).get()));
}

loss the format params in sub function

Compilation error if method argument got re-assigned

Only happens when the argument is second or later. First argument is ok.

Code

 	public Object test(){
 		return _test(1L,"").unwrap(Mono.class);
 	}
 	
 	@Async
 	private JPromise<String> _test(Long a, String b){
 		b = Promises.from(findById()).await();
 		
 		return Promises.from(b);
 	}
 	
 	private Mono<String> findById(){
 		return Mono.just("hi");
 	}

Error

[ERROR] cannot find symbol
  symbol:   variable tmp$$1642
  location: class TestController

The following 2 cases has no issue.

  1. First argument get re-assigned
 	public Object test(){
 		return _test("").unwrap(Mono.class);
 	}
 	
 	@Async
 	private JPromise<String> _test(String b){
 		b = Promises.from(findById()).await();
 		
 		return Promises.from(b);
 	}
 	
 	private Mono<String> findById(){
 		return Mono.just("hi");
 	}
  1. No re-assignment. Directly returned
 	public Object test(){
 		return _test(1L,"").unwrap(Mono.class);
 	}
 	
 	@Async
 	private JPromise<String> _test(Long a, String b){
 		return Promises.from(findById()).await();
 	}
 	
 	private Mono<String> findById(){
 		return Mono.just("hi");
 	}

Not working on Android

I marked my method @async and didn't call it in a lambda or anonymous class but still got the following error:

java.lang.UnsupportedOperationException: The method "JPromise#await" should be invoked in an async method. An async method is a method annotated with @JAsync and returning the JPromise object. The method should not be a lambda method or in an anonymous class
	at io.github.vipcxj.jasync.reactive.ReactorPromise.await(ReactorPromise.java:450)

Relevant code below:

@Async
public void loadAd(){
	try{
		AdManager.instance().interstitial().await();
		...
	}catch(Exception e){
		e.printStackTrace();
	}
}

public class AdManager extends com.customautosys.tuxfight.AdManager{
	@Override
	public JPromise<Void> interstitial(){
		if(AndroidLauncher.getInstance().adView==null) return JAsync.error(new Exception("AdMob not loaded yet"));
		return Promises.from(Mono.create(monoSink->AndroidLauncher.getInstance().runOnUiThread(()->InterstitialAd.load(
			AndroidLauncher.getInstance(),
			BuildConfig.DEBUG?"ca-app-pub-...":"ca-app-pub-...",
			new AdRequest.Builder().build(),
			new InterstitialAdLoadCallback(){
				@Override
				public void onAdLoaded(@NonNull InterstitialAd interstitialAd){
					interstitialAd.show(AndroidLauncher.getInstance());
					monoSink.success();
				}

				@Override
				public void onAdFailedToLoad(@NonNull LoadAdError loadAdError){
					monoSink.error(new Exception(loadAdError.getMessage()));
				}
			}
		))));
	}
}

java17模式匹配下if语句体编译报错

    @Async
    private JPromise<Void> simpleAwait() {
        Object obj = null;
        
        // 这种写法没问题
        if (obj instanceof String) {
            String stringObj = (String) obj;
            JAsync.just(null).await();
            System.out.println(stringObj);
        }

        // 这种写法编译报错
        if (obj instanceof String stringObj) {
            JAsync.just(null).await();
            System.out.println(stringObj);
        }
        return JAsync.just(null);
    }

    public static void main(String[] args) {
        App app = new App();
        System.out.println(app.simpleAwait().block());
    }


System.out.println(stringObj);
^
符号: 变量 stringObj
位置: 类 App

Issue with uncaught exceptions

If an exception is not caught and re-throw in the @Async method, the exception is gone.

Let me use your example (simplified) to explain. I assume salaryRepository.findSalaryByDeparmentEmployee returns a mono. If the repository throws an exception (returned error mono) , I would expect the mono unwrapped at getEmployeeSalaryByDepartment is an error mono too, but it is a null value mono instead. The exception is gone.

But if I catch and re-throw the error in the @Async method, then the unwrapped mono is an error mono.

I think it is reasonable to assume that uncaught exception should be passed alone instead of swallowed. If there is implementation difficulty, I think it would be better to reminder others to catch exceptions in the @Async method.

@RestController
@RequestMapping("/employees")
public class MyRestController {

    @Inject
    private SalaryRepository salaryRepository;

    @Async()
    private JPromise<Double> _getEmployeeTotalSalaryByDepartment(String department) {
        Salary salary = Promises.from(salaryRepository.findSalaryByDeparmentEmployee(department)).await();
        return JAsync.just(salary.total);
    }

    // This is a normal webflux method.
    @GetMapping("/{department}/salary")
    public Mono<Double> getEmployeeSalaryByDepartment(@PathVariable String department) { 
        // Use unwrap method to transform the JPromise object back to the Mono object.
        return _getEmployeeTotalSalaryByDepartment(department).unwrap(Mono.class);
    }
}

[Urgent] Concurrency issue when a @Async method is first time accessed

I encounter an ConcurrentModificationException as shown below.

It happens when a @ Async method is first time called by more than 1 threads. Say, if there's 2 thread concurrently access the method, one of them goes through, another thread will encounter ConcurrentModificationException.

03-25 14:43:14 ERROR - 
java.lang.RuntimeException: java.util.ConcurrentModificationException
	at io.github.vipcxj.jasync.runtime.java8.helpers.IndyHelpers.createFunction(IndyHelpers.java:83) ~[jasync-runtime-0.1.9.jar:?]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Assembly trace from producer [reactor.core.publisher.MonoError] :
	reactor.core.publisher.Mono.error(Mono.java:314)
	[SKIPPED...]
Original Stack Trace:
		at io.github.vipcxj.jasync.runtime.java8.helpers.IndyHelpers.createFunction(IndyHelpers.java:83) ~[jasync-runtime-0.1.9.jar:?]
		at io.github.vipcxj.jasync.runtime.java8.helpers.IndyHelper.voidPromiseFunction(IndyHelper.java:139) ~[jasync-runtime-0.1.9.jar:?]
		[SKIPPED...]
		at java.lang.Thread.run(Thread.java:833) [?:?]
Caused by: java.util.ConcurrentModificationException
	at java.util.HashMap.computeIfAbsent(HashMap.java:1221) ~[?:?]
	at io.github.vipcxj.jasync.runtime.java8.helpers.IndyHelpers.createFunction(IndyHelpers.java:51) ~[jasync-runtime-0.1.9.jar:?]
	at io.github.vipcxj.jasync.runtime.java8.helpers.IndyHelper.voidPromiseFunction(IndyHelper.java:139) ~[jasync-runtime-0.1.9.jar:?]
	[SKIPPED...]
	at java.lang.Thread.run(Thread.java:833) [?:?]

I look into it a bit, and I am pretty sure a lock is required at here.

IndyHelpers.java (Original)

 protected  <T> T createFunction(
            Class<T> proxyType, String proxyMethodName, MethodType proxyMethodType,
            String implMethodName, MethodType implMethodType,
            boolean isStatic, Object thisObj, Object... args
    ) {
        try {
            final CallSite site = callSites.computeIfAbsent(implMethodName, k -> {

Suggested change:

 protected  <T> T createFunction(
            Class<T> proxyType, String proxyMethodName, MethodType proxyMethodType,
            String implMethodName, MethodType implMethodType,
            boolean isStatic, Object thisObj, Object... args
    ) {
        try {
            // Get it without locking first
            CallSite site = callSites.get(implMethodName);
            if(site == null) {
                synchronized {
                    //computeIfAbsent is still required here, as there may be multiple threads pass the if condition
                    site = callSites.computeIfAbsent(implMethodName, k -> {

This issue is super important to fix. Would you please release a new version after fixing it? Thanks a lot!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.