Java实战:一个类让Java也用上JS的async/await

前言

前情提要,这个类其实是一个一时兴起的玩具,或者可以成为一个有意思的美丽小废物。

有一天在写js的时候,用async/await就能简单实现异步操作,一个promise就能优雅地处理异步流程,那是不是java也可以?

虽然completablefuture已经很全面了,但是写起来还是有点麻烦、冗长。

能不能在Java里也搞个类似的东西,让异步代码写起来更轻松呢?

心动不如立即行动,开整!

正文

💡 最初的痛点

我们平时是怎么处理Java中的异步操作的?是不是到处都是这样的代码:

java 复制代码
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 一些耗时操作
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return "操作结果";
});

future.thenAccept(result -> {
    System.out.println("获得结果: " + result);
}).exceptionally(ex -> {
    System.err.println("发生错误: " + ex.getMessage());
    return null;
});

每次都得写这种冗长的代码,链式调用虽然比回调嵌套好,但还是不够直观,尤其是当你需要在异步操作之间传递数据时,代码会变得更加复杂。

🚀 灵感来源:async/await

有次写js时,看着async/await写异步代码这么爽,就在想:Java能不能也整一个这么方便的东西?

javascript 复制代码
async function fetchData() {
    try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('获取数据失败:', error);
    }
}

看看上面的JavaScript代码,是不是非常清晰?异步操作看起来就像同步代码一样,没有回调,没有链式调用,简洁明了。

那么,我们能否在Java中也实现类似的效果呢?

但是受限于Java的特性,我们没办法去百分百还原,但是也可以通过 import static 和函数调用来尽可能的模仿。

如果能这样写Java代码,那岂不是很优雅:

java 复制代码
// 定义异步任务
Promise<String> promise = async(() -> {
    // 耗时操作
    Thread.sleep(1000);
    return "异步操作完成";
});

// 等待结果,就像JavaScript的await
String result = await(promise);
System.out.println(result);

简洁、直观,没有嵌套的回调,没有复杂的链式调用,这就是我想要的美丽小废物!

🚀 开整

实现这个想法其实并不复杂。我们需要两个核心组件:

  1. Promise:表示异步操作的结果
  2. asyncawait方法:用于创建和等待异步任务

核心思路是:

  • 使用虚拟线程执行异步任务
  • 用Promise封装异步结果
  • 提供await方法阻塞当前线程直到Promise完成

至于为啥我要用虚拟线程,因为最近的项目都是JDK21,虚拟线程真的太香了,强烈建议使用。

java 复制代码
public static <T> Promise<T> async(Task<T> task) {
    Promise<T> promise = new Promise<>();
    // 使用虚拟线程来异步执行任务
    Thread.ofVirtual().start(() -> {
        try {
            // 如果任务成功完成,则用结果解析promise
            promise.resolve(task.run());
        } catch (Throwable e) {
            // 如果任务抛出异常,则拒绝promise并传递异常
            promise.reject(e);
        }
    });
    return promise;
}

public static <T> T await(Promise<T> promise) {
    return promise.await();
}

Promise类内部使用CompletableFuture实现,但对外提供了更简单的API:

java 复制代码
public static class Promise<T> {
    private final CompletableFuture<T> future = new CompletableFuture<>();

    public void resolve(T value) {
        future.complete(value);
    }

    public void reject(Throwable ex) {
        future.completeExceptionally(ex);
    }

    public T await() {
        return future.join();
    }
}

是不是非常简单。

其实就是对 CompletableFuture 做了个包装,实现了一点点有趣的能力。

🪣 完整代码

这里贴上完整的代码。

也可以直接去Github仓库里看,有丰富的使用示例和单元测试,这是跳转地址(PS.还没整理好,整理好之后就贴上来)。

java 复制代码
import java.util.concurrent.CompletableFuture;

/**
 * 一个提供类似JavaScript async/await功能的Java工具类。
 * 该实现使用Java虚拟线程来执行异步任务,
 * 并提供简单的Promise-based API来处理异步操作。
 * 
 * <p>使用示例:</p>
 * <pre>{@code
 * // 定义一个异步任务
 * var promise = Async.async(() -> {
 *     // 一些耗时操作
 *     Thread.sleep(1000);
 *     return "异步操作的结果";
 * });
 * 
 * // 等待结果
 * String result = Async.await(promise);
 * }</pre>
 * 
 * @author easyboot
 */
public class Async {

    /**
     * 使用虚拟线程异步执行任务。
     * 
     * @param <T> 任务返回结果的类型
     * @param task 要异步执行的任务
     * @return 一个Promise对象,将来会被解析为任务的结果或被拒绝并包含异常
     */
    public static <T> Promise<T> async(Task<T> task) {
        Promise<T> promise = new Promise<>();
        // 使用虚拟线程来异步执行任务
        Thread.ofVirtual().start(() -> {
            try {
                // 如果任务成功完成,则用结果解析promise
                promise.resolve(task.run());
            } catch (Throwable e) {
                // 如果任务抛出异常,则拒绝promise并传递异常
                promise.reject(e);
            }
        });
        return promise;
    }

    /**
     * 等待promise被解析并返回其结果。
     * 此方法会阻塞当前线程直到promise被履行。
     * 如果promise被拒绝,异常将被抛出。
     * 
     * @param <T> 结果的类型
     * @param promise 要等待的promise
     * @return promise的解析值
     * @throws RuntimeException 如果promise被异常拒绝
     */
    public static <T> T await(Promise<T> promise) {
        return promise.await();
    }

    /**
     * Promise类表示异步操作的最终完成。
     * 类似于JavaScript的Promise,它可以处于三种状态之一:
     * 等待中(pending)、已完成(fulfilled/resolved)或已拒绝(rejected)。
     * 
     * @param <T> promise将被解析的值的类型
     */
    public static class Promise<T> {
        // 使用CompletableFuture来实现Promise功能
        private final CompletableFuture<T> future = new CompletableFuture<>();

        /**
         * 用给定的值解析promise。
         * 
         * @param value 用于解析promise的值
         */
        public void resolve(T value) {
            future.complete(value);
        }

        /**
         * 用给定的异常拒绝promise。
         * 
         * @param ex 用于拒绝promise的异常
         */
        public void reject(Throwable ex) {
            future.completeExceptionally(ex);
        }

        /**
         * 等待promise被解析并返回其值。
         * 此方法会阻塞当前线程直到promise被履行。
         * 
         * @return promise的解析值
         * @throws RuntimeException 如果promise被异常拒绝
         */
        public T await() {
            return future.join();
        }

        /**
         * 将此Promise转换为CompletableFuture。
         * 
         * @return 支持此Promise的CompletableFuture
         */
        public CompletableFuture<T> toFuture() {
            return future;
        }
    }

    /**
     * 表示可以异步运行的任务的函数式接口。
     * 
     * @param <T> 任务返回的结果类型
     */
    @FunctionalInterface
    public interface Task<T> {
        /**
         * 执行任务并返回结果。
         * 
         * @return 任务的结果
         * @throws Exception 如果执行过程中发生错误
         */
        T run() throws Exception;
    }

}

🐬 使用示例

下面是一个简单的使用示例,展示了如何使用Async类进行异步编程:

java 复制代码
// 创建一个异步任务
Promise<String> promise = async(() -> {
    System.out.println("异步任务开始执行...");
    Thread.sleep(2000); // 模拟耗时操作
    System.out.println("异步任务执行完成");
    return "操作结果";
});

System.out.println("等待异步任务完成...");
String result = await(promise); // 阻塞直到任务完成
System.out.println("获得结果: " + result);

输出结果:

erlang 复制代码
异步任务开始执行...
等待异步任务完成...
异步任务执行完成
获得结果: 操作结果

🚀 复杂场景:串行和并行执行

这个简单的工具类也能处理更复杂的异步场景:

串行执行多个异步任务:

java 复制代码
Promise<String> promise1 = async(() -> {
    Thread.sleep(1000);
    return "第一步";
});

String step1Result = await(promise1);

Promise<String> promise2 = async(() -> {
    Thread.sleep(1000);
    return step1Result + " -> 第二步";
});

String finalResult = await(promise2);
System.out.println(finalResult); // 输出: 第一步 -> 第二步

并行执行多个异步任务:

java 复制代码
Promise<String> promise1 = async(() -> {
    Thread.sleep(1000);
    return "任务1结果";
});

Promise<String> promise2 = async(() -> {
    Thread.sleep(1000);
    return "任务2结果";
});

// 两个任务并行执行,然后等待所有结果
String result1 = await(promise1);
String result2 = await(promise2);

System.out.println(result1 + " + " + result2);

🤔 改进建议

当然,这个类还有很大的改进空间,我简单列几个,列位看官可以根据自己的真实场景再逐步进行优化:

  • 添加超时机制,避免无限等待
  • 支持取消操作
  • 添加Promise组合操作,如all、race等
  • 使用线程池而非每次创建新线程,以支持JDK 8-17
  • 添加更多的错误处理机制
  • 支持异步操作的进度通知

革命尚未成功,同志仍需努力。

总结

🤌 一点点经验

先来点经验总结,仁者见仁,智者见智:

  • 借鉴其他语言的优秀特性可以带来新的编程体验
  • 虚拟线程是Java异步编程的一大进步
  • 简单的封装可以大大提升代码的可读性和维护性
  • 异步编程的核心是关注点分离,让代码结构更清晰
  • 即使是玩具项目,也能带来实际的启发和价值

⚖️ 写在最后

这个小小的玩具类并不复杂,技术含量也不高,核心代码不过才几十行,但却能让我们在Java中也能感受到类似 JS 中 async/await的体验。

这就是开发的乐趣所在:用简单的代码解决实际的问题,让开发体验更加愉悦。

而这大概也是写代码的意义:不是为了炫技,而是为了解决问题,让复杂的事情变得简单。

就像一把好用的工具,真正的价值在于它能让使用者专注于要解决的问题本身,而不是工具本身。

有时候,最好的代码就是那种让你忘记它存在的代码。

相关推荐
Pure03192 小时前
Spring 循环依赖问题
java·数据库·spring
路在脚下@2 小时前
Spring Security的@PreAuthorize注解为什么会知道用户角色?
java
黄焖鸡能干四碗2 小时前
固定资产管理系统(蓝牙标签打印+移动端Java+Vue+Uniapp源码)
java·开发语言·vue.js·eclipse·uni-app
皮皮林5512 小时前
Java jar 如何防止被反编译?代码写的太烂,害怕被人发现
java
程序猿毕设源码分享网2 小时前
springboot医院信管系统源码和论文
java·spring boot·后端
叫我阿柒啊3 小时前
Java全栈开发面试实战:从基础到微服务的全面解析
java· spring boot· vue.js· 微服务· rest api· 数据库· 测试
桦说编程4 小时前
数据丢失,而且不抛出并发异常,多线程使用HashMap踩坑
java·数据结构·后端
颜如玉4 小时前
Redis主从同步浅析
后端·开源·源码
奔跑吧邓邓子5 小时前
【Java实战⑨】Java集合框架实战:List集合深度剖析
java·实战·list·集合