Future/Promise là một mẫu lập trình bất đồng bộ biểu diễn một vùng chứa cho kết quả của một thao tác chưa hoàn thành. Theo Dart Documentation, 2025, Future là kết quả chỉ đọc của các tính toán trễ, Promise là hợp đồng chỉ ghi giúp điền kết quả này. Future được sử dụng trong Dart, Java, Scala và C++ cho các chuỗi then/catch.
Các điểm chính
Future/Promise là một cặp đối tượng kết nối với nhau chia kết quả bất đồng bộ thành hai vai trò. Promise có thể ghi kết quả (resolve) hoặc lỗi (reject). Future chỉ có thể đọc nó — thông qua callback (then, catchError) hoặc qua await. Sự phân tách này đảm bảo rằng người tiêu dùng không thể thay đổi kết quả, chỉ có thể sử dụng nó.
Ngôn ngữ Dart sử dụng Future như cơ chế bất đồng bộ chính của nó. Future<T> là một hợp đồng để nhận một giá trị kiểu T trong tương lai. Future có thể ở một trong ba trạng thái: unresolved (thao tác đang tiến hành), resolved with value (hoàn thành thành công) hoặc resolved with error (hoàn thành với lỗi). Một khi chuyển sang trạng thái resolved, Future không bao giờ thay đổi.
Promise trong JavaScript tương tự như Future trong Dart, nhưng với API phong phú hơn. Promise nhận một executor — một hàm với các tham số resolve và reject. Các phương thức tĩnh Promise.all, Promise.race, Promise.allSettled cho phép kết hợp nhiều promise. Các ứng dụng hiện đại sử dụng cú pháp async/await trên Promise.
Future hoạt động dựa trên event loop — một vòng lặp sự kiện xử lý các thao tác bất đồng bộ mà không chặn luồng chính. Khi một hàm bất đồng bộ được gọi (ví dụ, http.get), nó ngay lập tức trả về một Future. Yêu cầu thực tế chạy ở nền, trong khi event loop tiếp tục xử lý các sự kiện khác. Sau khi thao tác hoàn thành, Future chuyển sang trạng thái resolved và then được gọi.
Event loop là cơ chế chính mà Dart và JavaScript được xây dựng trên đó. Event loop duy trì hai hàng đợi: hàng đợi microtask (các tác vụ ngắn, được thực thi trước sự kiện tiếp theo) và hàng đợi sự kiện (sự kiện IO, bộ đếm thời gian). Future.then đặt một callback vào hàng đợi microtask, Future.microtask — thực thi ưu tiên trước khung hình tiếp theo.
Các chuỗi then() cho phép xử lý tuần tự các kết quả thao tác bất đồng bộ mà không có callback hell. Mỗi then() nhận kết quả của bước trước và trả về một Future mới. catchError() ở cuối chuỗi xử lý mọi lỗi phát sinh ở bất kỳ bước nào. Điều này làm cho mã bất đồng bộ trở nên tuyến tính và dễ đọc.
Trong Java, CompletableFuture mở rộng khái niệm Future/Promise. Phương thức supplyAsync() khởi chạy một tác vụ bất đồng bộ trong ForkJoinPool.commonPool(). thenApply() biến đổi kết quả, thenCompose() mở một CompletableFuture lồng nhau. exceptionally() xử lý lỗi bằng cách trả về giá trị mặc định. allOf() và anyOf() hoạt động như Promise.all và Promise.race trong JavaScript. CompletableFuture hỗ trợ hoàn thành thủ công qua complete() và completeExceptionally() — điều này tương tự như Promise resolve/reject.
Promise là một hợp đồng với một trong ba trạng thái: pending (đang chờ), fulfilled (đã thực hiện) hoặc rejected (bị từ chối). Một khi chuyển sang fulfilled hoặc rejected, Promise không bao giờ thay đổi — điều này đảm bảo tính bất biến của kết quả cho tất cả người tiêu dùng. Phương thức .then() được gọi bất đồng bộ sau khi chuyển sang bất kỳ trạng thái hoàn thành nào.
Promise.all chờ tất cả các promise được truyền vào hoàn thành và trả về một mảng kết quả theo cùng thứ tự. Nếu một promise thất bại với lỗi — toàn bộ Promise.all chuyển sang rejected. Promise.race trả về kết quả của promise hoàn thành đầu tiên (thành công hoặc lỗi). Promise.allSettled chờ tất cả promise bất kể lỗi — trả về trạng thái của từng cái.
Chuỗi (Chaining) là lợi thế chính của Promise so với callback. Mỗi .then() trả về một Promise mới, cho phép các chuỗi có độ dài tùy ý. Lỗi ở bất kỳ mắt xích nào trong chuỗi lan truyền đến .catch() gần nhất. Phương thức finally() thực thi mã sau khi chuỗi hoàn thành bất kể kết quả — để dọn dẹp tài nguyên.
Ví dụ đầu tiên — tạo Future qua hàm async và xử lý bằng then/catchError:
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 1))
return "Dữ liệu đã tải"
}
fetchData()
.then((result) => print(result))
.catchError((error) => print("Lỗi: $error"))
Ví dụ thứ hai — kết hợp nhiều Future với Future.wait:
Future<List<String>> loadAll() async {
final futures = [
fetchFromApi("/users"),
fetchFromApi("/posts"),
fetchFromApi("/comments")
]
final results = await Future.wait(futures)
return results
}
Ví dụ thứ ba — Promise trong JavaScript với tạo thủ công và chuỗi then:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Xong")
}, 1000)
})
promise
.then(value => console.log(value))
.catch(err => console.error(err))
Future/Promise khác với phong cách callback ở chỗ nó trả về một đối tượng hợp đồng thay vì nhận hàm callback làm đối số. Phong cách callback dẫn đến lồng ghép (callback hell), trong khi Future cho phép xây dựng các chuỗi then() phẳng. Cú pháp async/await làm cho mã Future thậm chí tuyến tính hơn, giống với mã đồng bộ.
Future biểu diễn chính xác một kết quả bất đồng bộ — một lần phát, sau đó Future hoàn thành. Không giống như Observable (Rx, ReactiveX) hay Flow (Kotlin Coroutines), Future không hỗ trợ nhiều giá trị theo thời gian. Nếu bạn cần một luồng sự kiện — hãy sử dụng Stream trong Dart, Observable trong Rx hoặc Flow trong Kotlin.
CompletableFuture trong Java là một tương tự của Promise với API mở rộng: supplyAsync, thenApply, thenCompose, exceptionally. CompletableFuture cho phép kết hợp nhiều thao tác bất đồng bộ qua allOf, anyOf và cũng cho phép chỉ định Executor tùy chỉnh cho việc thực thi. Scala Future sử dụng ExecutionContext để quản lý nhóm luồng và hỗ trợ kết hợp qua for-comprehension.
Trong Dart, mẫu Future khác với Promise trong JavaScript ở chỗ Dart có kiểu mạnh: Future<String> đảm bảo kiểu của giá trị trả về. Các hàm async trong Dart phải trả về Future và trình biên dịch kiểm tra rằng tất cả các nhánh đều trả về kiểu chính xác. Future.wait tương đương với Promise.all — nó nhận Iterable<Future<T>> và trả về Future<List<T>>. Future.delayed và Future.error là các factory tiện lợi để kiểm thử các kịch bản bất đồng bộ.
Future/Promise được triển khai trong Python qua concurrent.futures.Future và asyncio.Future. concurrent.futures.ThreadPoolExecutor.submit() trả về Future, trên đó có thể gọi result() — một lời gọi chặn. asyncio.Future là một vùng chứa tương thích await cho kết quả bất đồng bộ trong Asyncio. asyncio.gather() chạy nhiều coroutine song song và trả về danh sách kết quả, tương tự như Promise.all trong JavaScript. Python cũng sử dụng asyncio.ensure_future dựa trên callback và chuỗi add_done_callback để kết hợp các thao tác bất đồng bộ, nhưng trong mã hiện đại, cú pháp async/await với asyncio.Task được ưu tiên hơn.
Trong phát triển di động, Future được sử dụng tích cực trong các ứng dụng Flutter. Mỗi yêu cầu mạng từ gói http hoặc dio trả về Future. FutureBuilder là một widget nhận Future và trạng thái snapshot: connectionDone, hasData, hasError. Khi dữ liệu được nhận, snapshot.data chứa kết quả Future. StreamBuilder là tương tự cho nhiều giá trị. FutureBuilder loại bỏ việc quản lý trạng thái tải thủ công qua setState.
Scala Future sử dụng ExecutionContext một cách ngầm định — nhóm luồng mà trên đó tác vụ bất đồng bộ thực thi. Việc kết hợp qua map, flatMap, filter hoạt động vì Future là một monad. for-comprehension cho phép kết hợp tuần tự nhiều Future mà không lồng ghép: for { a <- futureA; b <- futureB } yield a + b. Future.sequence hoạt động như Promise.all. Dự phòng lỗi được triển khai qua recover và recoverWith.
Future/Promise trong C++ được đại diện bởi các lớp std::future và std::promise từ thư viện <future>. std::async khởi chạy một hàm bất đồng bộ và trả về std::future. std::promise cho phép đặt giá trị thủ công qua set_value() hoặc lỗi qua set_exception(). Không giống Dart và JavaScript, std::future.get() chặn luồng cho đến khi nhận được kết quả — đây là một sự khác biệt quan trọng cần cân nhắc khi thiết kế các ứng dụng C++ đa luồng.
Khi thiết kế các API trả về Future, hãy tuân thủ quy tắc nguồn sự thật duy nhất: mỗi yêu cầu bất đồng bộ được tạo một lần và được lưu vào bộ nhớ đệm qua Future.memoize. Nếu hai thành phần yêu cầu cùng một dữ liệu, chúng nhận được cùng một Future — điều này ngăn chặn các yêu cầu trùng lặp. Đối với timeout, hãy sử dụng Future.timeout (Dart) hoặc Promise.race với timeout để đảm bảo thao tác bất đồng bộ hoàn thành trong một khoảng thời gian nhất định.
Mẫu Retry trong Future được triển khai qua một chuỗi đệ quy: khi lỗi trong catch, cùng một hàm bất đồng bộ được gọi với một độ trễ. Exponential backoff tăng khoảng cách giữa các lần thử: 1 giây — 2 giây — 4 giây — lên đến tối đa 30 giây. Trong Dart, điều này được đóng gói trong một extension trên Future: future.retry(maxRetries: 3, delay: Duration(seconds: 1)). Trong Scala, Future.zip kết hợp hai kết quả độc lập thành một tuple — một thay thế cho Promise.all cho hai Future. Hủy bỏ không được hỗ trợ trong Future tiêu chuẩn, nhưng được triển khai qua CancellationToken hoặc AbortController trong JavaScript.
Câu hỏi thường gặp
Future là vùng chứa chỉ đọc để đọc kết quả bất đồng bộ qua then hoặc await. Promise là hợp đồng chỉ ghi nhận giá trị qua resolve() và liên kết nó với Future. Trong Dart và JavaScript, các khái niệm này được kết hợp: Future/Promise là một lớp. Trong Java và Scala, chúng được tách riêng.
Future biểu diễn một kết quả bất đồng bộ duy nhất — nó phát ra chính xác một giá trị và hoàn thành. Observable (Rx) phát ra nhiều giá trị theo thời gian — đó là một luồng bất đồng bộ. Nếu bạn cần một phản hồi HTTP — hãy sử dụng Future. Nếu bạn cần một luồng thay đổi cơ sở dữ liệu — hãy sử dụng Observable hoặc Stream.
async/await là cú pháp đường bao quanh Future/Promise. Từ khóa async đánh dấu một hàm là bất đồng bộ — nó trả về Future. await tạm dừng thực thi cho đến khi Future hoàn thành, mà không chặn luồng. Trong Dart, JavaScript, Python và C#, async/await đã thay thế các chuỗi then trong hầu hết các kịch bản.
Trong Dart, hãy sử dụng .catchError() ở cuối chuỗi — nó bắt các ngoại lệ ở bất kỳ giai đoạn nào. Trong JavaScript — .catch(). Dart cũng có try/await/catch bên trong các hàm async. Để phục hồi lỗi, hãy sử dụng .catchError() với giá trị mặc định hoặc một Future mới.
Promise.all nhận một mảng các promise và trả về một Promise duy nhất được giải quyết với một mảng kết quả. Tất cả các promise chạy song song — điều này tăng tốc các thao tác độc lập (ví dụ, tải nhiều tệp). Nếu bất kỳ promise nào bị từ chối — toàn bộ Promise.all bị từ chối với cùng lỗi đó.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm