James Andrew
James Andrew

Reputation: 207

Wait for a CompletableFuture to be complete, then return a different value?

I have a CompletableFuture<Void> that calls an asynchronous method whose return type I can't change, or anything about it.

I want to wait for this method to be complete (I manually complete it), and then return a String value, how would I do this?

public String getServer(Player p) {
    FutureServer f = new FutureServer(CompletableFuture.runAsync(() -> {
        sendUTF(p, "GetServer");
        try {
            Thread.sleep(10000); //so the future doesnt complete itself
        } catch (Exception e) {
            e.printStackTrace();
        }
    }), p.getUniqueId().toString());
    serverSet.add(f);
    String server = "";

    //server isn't final so I can't use it in the lambda
    f.getFutureVoid().whenComplete(v -> server = f.getServer()); 

    return server;
}

public class FutureServer {

    private CompletableFuture<Void> futureVoid;
    private String s;
    private String uuid;

    public FutureServer(CompletableFuture<Void> futureVoid, String uuid) {
        this.futureVoid = futureVoid;
        this.uuid = uuid;
    }

    public String getUuid() {
        return uuid;
    }

    public CompletableFuture<Void> getFutureVoid() {
        return futureVoid;
    }

    public boolean hasServer() {
        return s != null;
    }

    public void setServer(String s) {
        this.s = s;
    }

    public String getServer() {
        return s;
    }
}

I want to set string to equal FutureServer#getServer() (own method), but I need to wait until the CompletableFuture<Void> is completed. What do I do?

This is the method that gets called async and is unchangeable... the method I use that calls this other method asynchronously is sendUTF().

@Override
public void onPluginMessageReceived(String s, Player p, byte[] bytes) {
    if (!s.equals("BungeeCord")) return;

    ByteArrayDataInput in = ByteStreams.newDataInput(bytes);
    String subChannel = in.readUTF();

    switch(subChannel) {
        case "GetServer":
            String server = in.readUTF();
            serverSet.stream().filter(f -> f.getUuid().equals(p.getUniqueId().toString())).findFirst().ifPresent(f -> {
                f.setServer(server); //getting the string I need and placing it into this object
                f.getFutureVoid().complete(null); //completing the void future manually
            });
            break;
    }

}

Upvotes: 2

Views: 5254

Answers (2)

Didier L
Didier L

Reputation: 20579

The simplest solution is simply to join() on that future, either with:

public String getServer(Player p) {
    FutureServer f = new FutureServer(CompletableFuture.runAsync(() -> {
        sendUTF(p, "GetServer");
        try {
            Thread.sleep(10000); //so the future doesnt complete itself
        } catch (Exception e) {
            e.printStackTrace();
        }
    }), p.getUniqueId().toString());
    serverSet.add(f);

    return f.getFutureVoid().thenApply(v -> f.getServer()).join(); 
}

which can easily be transformed to return a CompletableFuture<String> instead by removing the .join(), or also:

public String getServer(Player p) {
    FutureServer f = new FutureServer(CompletableFuture.runAsync(() -> {
        sendUTF(p, "GetServer");
        try {
            Thread.sleep(10000); //so the future doesnt complete itself
        } catch (Exception e) {
            e.printStackTrace();
        }
    }), p.getUniqueId().toString());
    serverSet.add(f);

    f.getFutureVoid().join();
    return f.getServer();
}

Upvotes: 0

tgdavies
tgdavies

Reputation: 11421

You could do this:

final AtomicReference<String> server = new AtomicReference<>("");

f.getFutureVoid().whenComplete(v -> server.set(f.getServer())).get(/* maybe add a timeout */); 

return server.get();

Upvotes: 3

Related Questions