Java CompletableFuture.handle: รวมทางสำเร็จและล้มเหลวไว้ที่ boundary เดียว
async pipeline มักต้องเปลี่ยนทั้งค่าปกติและ exception ให้เป็น response type เดียวก่อนออกจาก service CompletableFuture.handle เรียก BiFunction ไม่ว่า stage ก่อนหน้าจะสำเร็จหรือล้มเหลว โดยฝั่งที่ไม่เกิดจะเป็น null แล้วคืน future ของค่าที่แปลงแล้วครับ
import java.util.concurrent.CompletableFuture;
public class Main {
record Outcome<T>(T value, String error) {
static <T> Outcome<T> ok(T value) { return new Outcome<>(value, null); }
static <T> Outcome<T> failed(String error) { return new Outcome<>(null, error); }
}
static CompletableFuture<Outcome<Integer>> parseAsync(String raw) {
return CompletableFuture
.supplyAsync(() -> Integer.parseInt(raw))
.handle((value, error) -> error == null
? Outcome.ok(value)
: Outcome.failed("invalid integer"));
}
public static void main(String[] args) {
assert parseAsync("42").join().equals(Outcome.ok(42));
assert parseAsync("bad").join().equals(Outcome.failed("invalid integer"));
System.out.println("handle tests passed");
}
}
ต่างจากเพื่อนบ้านอย่างไร
exceptionally ทำงานเฉพาะทางล้มเหลวและต้องคืน fallback ชนิดเดียวกับผลปกติ whenComplete เห็นทั้ง value/error แต่มีไว้ทำ side effect และส่ง outcome เดิมต่อ ส่วน handle เปลี่ยนทั้งสองทางเป็นชนิดใหม่ได้ จึงเหมาะกับ boundary ที่ต้องทำ error envelope หรือสรุป telemetry แล้วจบ chain
ต้องระวังว่า exception มักถูกห่อใน CompletionException อย่าเผย error.getMessage() ตรง ๆ แก่ผู้ใช้ เพราะอาจมีข้อมูลระบบภายในและชนิด wrapper อาจทำให้การจัดหมวดผิด แยก public error code ออกจาก diagnostic log และ unwrap เฉพาะเท่าที่นโยบายกำหนด
หาก callback ใน handle โยน exception ใหม่ future ผลลัพธ์ก็ล้มเหลวอีกครั้ง ดังนั้น mapping ที่ boundary ควรเล็ก deterministic และไม่ทำ I/O เพิ่ม หากต้อง retry ให้สร้างนโยบาย retry ก่อนมาถึง handle; การคืน fallback ในทุก exception โดยไม่แยก timeout, cancellation และ validation อาจทำให้ incident ถูกซ่อน
Recap
handleเห็นทั้ง value และ error และแปลงชนิดผลลัพธ์ได้- ใช้
whenCompleteสำหรับ observation และexceptionallyสำหรับ fallback ฝั่ง error - อย่าเผย raw exception message ข้าม trust boundary
- แยก cancellation/timeout จาก error ธุรกิจแทนการกลืนรวมครับ