You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX中如何让线程等待WebView内JavaScript执行完毕再继续?

解决方案:异步任务队列 + JS-Java 回调

要解决循环等待JS函数执行完毕的问题,核心思路是放弃同步循环,改用异步任务队列+回调机制,完全贴合JavaFX的单线程UI模型,既不会阻塞GUI,也能保证任务按顺序执行。

1. 实现Java回调类(供JS调用)

首先定义一个Java类,用于接收JS的完成通知,触发下一个任务:

import javafx.application.Platform;

public class JSCallback {
    private Runnable onComplete;

    public void setOnComplete(Runnable onComplete) {
        this.onComplete = onComplete;
    }

    // JS将调用此方法通知任务完成
    public void change() {
        // 确保回调在JavaFX应用线程执行
        Platform.runLater(() -> {
            if (onComplete != null) {
                onComplete.run();
            }
        });
    }
}

2. 重构startExp方法:用任务队列替代同步循环

把原来的嵌套循环逻辑拆成独立任务,存入队列,只有当前任务的JS函数执行完毕后,才会触发下一个任务:

import javafx.concurrent.Worker;
import javafx.beans.value.ChangeListener;
import java.util.Queue;
import java.util.LinkedList;
import java.util.concurrent.ThreadLocalRandom;
import java.net.URL;
import netscape.javascript.JSObject;

public void startExp() {
    root.setCenter(webView);

    // 初始化回调对象并注入到JS环境
    JSCallback callback = new JSCallback();
    webEngine.executeScript("window.invoke = " + JSObject.toJavaObject(callback));

    // 创建任务队列,存储所有待执行的页面加载任务
    Queue<Runnable> taskQueue = new LinkedList<>();

    // 填充任务队列:把原循环逻辑转为独立任务
    for (String expMode : expModes) {
        int r = -1;
        for (int j = 0; j < 2; j++) {
            int temp = r;
            while (r == temp) {
                r = ThreadLocalRandom.current().nextInt(0, 2);
            }
            final String mode = expMode + ".html";

            taskQueue.add(() -> {
                System.out.println(mode);
                URL url = this.getClass().getResource(mode);

                // 复用加载监听器,避免重复添加
                webEngine.getLoadWorker().stateProperty().removeListener(loadListener);
                webEngine.getLoadWorker().stateProperty().addListener(loadListener);

                webEngine.load(url.toString());
            });
        }
    }

    // 定义页面加载完成后的处理逻辑
    ChangeListener<Worker.State> loadListener = (ov, oldState, newState) -> {
        if (newState == Worker.State.SUCCEEDED) {
            System.out.println("页面加载完成,执行JS timer函数");
            // 设置回调:JS执行完毕后自动取下一个任务执行
            callback.setOnComplete(() -> {
                if (!taskQueue.isEmpty()) {
                    taskQueue.poll().run();
                }
            });
            // 传入实际的passage内容(原代码中传的是字符串'passage',这里修正)
            webEngine.executeScript("timer('" + passage + "');");
        }
    };

    // 启动第一个任务
    if (!taskQueue.isEmpty()) {
        taskQueue.poll().run();
    }
}

3. 修正HTML中的JS代码

确保JS在timer执行完毕后正确调用Java回调,同时重置计数器避免后续调用出错:

<script type="application/javascript">
    let i = 0;

    function timer(a) {
        const splitted = a.split(" ");
        const timerID = setInterval(highlight, 500);

        function highlight() {
            if (i === splitted.length) {
                clearInterval(timerID);
                // 调用Java端的回调通知任务完成
                if (window.invoke) {
                    invoke.change();
                }
                i = 0; // 重置计数器,避免下次调用直接结束
                return;
            }

            let markS = "<mark>";
            let markE = "</mark>";
            let temp = splitted[i];

            splitted[i] = markS + splitted[i] + markE;
            document.getElementById("demo").innerHTML = splitted.join(" ");
            splitted[i] = temp;

            i++;
        }
    }
</script>

方案说明

  • 无阻塞UI:所有任务调度都基于事件回调,不会卡住JavaFX应用线程,GUI始终保持响应。
  • 顺序执行:任务队列保证页面加载和JS执行严格按原循环顺序进行,只有当前任务完成才会启动下一个。
  • 线程安全:通过Platform.runLater()确保回调操作在JavaFX线程执行,符合WebView的线程要求。

内容的提问来源于stack exchange,提问作者asad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 20:05:30