Future/Promise는 아직 완료되지 않은 작업의 결과를 위한 컨테이너를 나타내는 비동기 프로그래밍 패턴입니다. Dart Documentation, 2025에 따르면 Future는 지연된 계산의 읽기 전용 결과이고, Promise는 이 결과를 채우는 쓰기 전용 계약입니다. Future는 Dart, Java, Scala 및 C++에서 then/catch 체인에 사용됩니다.
핵심 사항
Future/Promise는 비동기 결과를 두 가지 역할로 나누는 상호 연결된 객체의 쌍입니다. Promise는 결과(resolve) 또는 오류(reject)를 쓸 수 있습니다. Future는 callback(then, catchError) 또는 await를 통해서만 읽을 수 있습니다. 이러한 분리는 소비자가 결과를 변경할 수 없고 사용만 할 수 있음을 보장합니다.
Dart 언어는 Future를 주요 비동기 메커니즘으로 사용합니다. Future<T>는 미래에 T 유형의 값을 받기 위한 계약입니다. Future는 세 가지 상태 중 하나일 수 있습니다: unresolved(작업 진행 중), resolved with value(성공적으로 완료), resolved with error(오류로 완료). resolved 상태로 전환되면 Future는 절대 변경되지 않습니다.
JavaScript의 Promise는 Dart의 Future와 유사하지만 더 풍부한 API를 제공합니다. Promise는 실행기(resolve 및 reject 매개변수가 있는 함수)를 받습니다. 정적 메서드 Promise.all, Promise.race, Promise.allSettled는 여러 프로미스를 결합할 수 있습니다. 현대 애플리케이션은 Promise 위에 async/await 문법을 사용합니다.
Future는 이벤트 루프(메인 스레드를 차단하지 않고 비동기 작업을 처리하는 이벤트 주기)를 기반으로 작동합니다. 비동기 함수(예: http.get)가 호출되면 즉시 Future를 반환합니다. 실제 요청은 백그라운드에서 실행되고 이벤트 루프는 다른 이벤트를 계속 처리합니다. 작업이 완료되면 Future는 resolved 상태로 전환되고 then이 호출됩니다.
이벤트 루프는 Dart와 JavaScript가 구축된 핵심 메커니즘입니다. 이벤트 루프는 두 개의 큐를 유지합니다: 마이크로태스크 큐(다음 이벤트 전에 실행되는 짧은 작업)와 이벤트 큐(IO 이벤트, 타이머). Future.then은 마이크로태스크 큐에 callback을 배치하고, Future.microtask는 다음 프레임 전에 우선 실행됩니다.
then() 체인은 callback 지옥 없이 비동기 작업 결과의 순차적 처리를 가능하게 합니다. 각 then()은 이전 단계의 결과를 받고 새 Future를 반환합니다. 체인 끝의 catchError()는 모든 단계에서 발생한 모든 오류를 처리합니다. 이렇게 하면 비동기 코드가 선형적이고 읽기 쉬워집니다.
Java에서 CompletableFuture는 Future/Promise 개념을 확장합니다. supplyAsync() 메서드는 ForkJoinPool.commonPool()에서 비동기 작업을 시작합니다. thenApply()는 결과를 변환하고, thenCompose()는 중첩된 CompletableFuture를 풉니다. exceptionally()는 기본값을 반환하여 오류를 처리합니다. allOf()와 anyOf()는 JavaScript의 Promise.all 및 Promise.race처럼 작동합니다. CompletableFuture는 complete() 및 completeExceptionally()를 통한 수동 완료를 지원합니다 — 이는 Promise resolve/reject와 유사합니다.
Promise는 세 가지 상태 중 하나를 가진 계약입니다: pending(대기 중), fulfilled(이행됨), rejected(거부됨). fulfilled 또는 rejected로 전환되면 Promise는 절대 변경되지 않습니다 — 이는 모든 소비자에 대한 결과의 불변성을 보장합니다. .then() 메서드는 완료된 상태로 전환된 후 비동기적으로 호출됩니다.
Promise.all은 전달된 모든 프로미스의 완료를 기다리고 동일한 순서로 결과 배열을 반환합니다. 하나의 프로미스가 오류로 실패하면 — 전체 Promise.all이 rejected로 전환됩니다. Promise.race는 가장 먼저 완료된 프로미스의 결과(성공 또는 오류)를 반환합니다. Promise.allSettled는 오류에 관계없이 모든 프로미스를 기다립니다 — 각각의 상태를 반환합니다.
체이닝은 callback에 비해 Promise의 주요 장점입니다. 각 .then()은 새 Promise를 반환하여 임의 길이의 체인을 가능하게 합니다. 체인의 모든 링크에서 발생한 오류는 가장 가까운 .catch()로 전파됩니다. finally() 메서드는 결과에 관계없이 체인 완료 후 코드를 실행합니다 — 리소스 정리를 위해 사용됩니다.
첫 번째 예제 — async 함수를 통해 Future를 만들고 then/catchError로 처리하기:
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 1))
return "데이터 로드됨"
}
fetchData()
.then((result) => print(result))
.catchError((error) => print("오류: $error"))
두 번째 예제 — Future.wait로 여러 Future 결합하기:
Future<List<String>> loadAll() async {
final futures = [
fetchFromApi("/users"),
fetchFromApi("/posts"),
fetchFromApi("/comments")
]
final results = await Future.wait(futures)
return results
}
세 번째 예제 — 수동 생성 및 then 체인을 사용한 JavaScript의 Promise:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("완료")
}, 1000)
})
promise
.then(value => console.log(value))
.catch(err => console.error(err))
Future/Promise는 callback 함수를 인수로 받는 대신 계약 객체를 반환한다는 점에서 callback 스타일과 다릅니다. Callback 스타일은 중첩(callback 지옥)으로 이어지지만, Future는 평평한 then() 체인을 구축할 수 있습니다. async/await 문법은 Future 코드를 더욱 선형적으로 만들어 동기 코드와 유사하게 만듭니다.
Future는 정확히 하나의 비동기 결과를 나타냅니다 — 한 번의 방출 후 Future가 완료됩니다. Observable(Rx, ReactiveX)이나 Flow(Kotlin Coroutines)와 달리 Future는 시간에 따른 여러 값을 지원하지 않습니다. 이벤트 스트림이 필요한 경우 — Dart의 Stream, Rx의 Observable 또는 Kotlin의 Flow를 사용하세요.
Java의 CompletableFuture는 확장 API를 갖춘 Promise의 아날로그입니다: supplyAsync, thenApply, thenCompose, exceptionally. CompletableFuture는 allOf, anyOf를 통해 여러 비동기 작업을 결합할 수 있으며 실행을 위한 사용자 정의 Executor를 지정할 수도 있습니다. Scala Future는 스레드 풀을 관리하기 위해 ExecutionContext를 암시적으로 사용하고 for-comprehension을 통한 구성을 지원합니다.
Dart에서 Future 패턴은 JavaScript Promise와 달리 엄격한 타입 지정이 있습니다: Future<String>은 반환 값의 타입을 보장합니다. Dart의 async 함수는 Future를 반환해야 하며 컴파일러는 모든 경로가 올바른 타입을 반환하는지 확인합니다. Future.wait는 Promise.all과 동일합니다 — Iterable<Future<T>>를 받아 Future<List<T>>를 반환합니다. Future.delayed와 Future.error는 비동기 시나리오 테스트를 위한 편리한 팩토리입니다.
Future/Promise는 Python에서 concurrent.futures.Future 및 asyncio.Future를 통해 구현됩니다. concurrent.futures.ThreadPoolExecutor.submit()은 Future를 반환하며 result()를 호출할 수 있습니다 — 차단 호출입니다. asyncio.Future는 Asyncio에서 비동기 결과를 위한 await 호환 컨테이너입니다. asyncio.gather()는 여러 코루틴을 병렬로 실행하고 결과 목록을 반환하며 JavaScript의 Promise.all과 유사합니다. Python은 또한 비동기 작업 구성을 위해 callback 기반의 asyncio.ensure_future 및 add_done_callback 체인을 사용하지만 현대 코드에서는 asyncio.Task와 함께 async/await 문법이 선호됩니다.
모바일 개발에서 Future는 Flutter 애플리케이션에서 활발히 사용됩니다. http 또는 dio 패키지의 모든 네트워크 요청은 Future를 반환합니다. FutureBuilder는 Future와 스냅샷 상태(connectionDone, hasData, hasError)를 받는 위젯입니다. 데이터를 수신하면 snapshot.data에 Future 결과가 포함됩니다. StreamBuilder는 여러 값을 위한 아날로그입니다. FutureBuilder는 setState를 통한 수동 로딩 상태 관리를 제거합니다.
Scala Future는 암시적으로 ExecutionContext를 사용합니다 — 비동기 작업이 실행되는 스레드 풀입니다. map, flatMap, filter를 통한 구성은 Future가 모나드이기 때문에 작동합니다. for-comprehension은 중첩 없이 여러 Future를 순차적으로 결합할 수 있습니다: for { a <- futureA; b <- futureB } yield a + b. Future.sequence는 Promise.all처럼 작동합니다. 오류 폴백은 recover 및 recoverWith를 통해 구현됩니다.
C++의 Future/Promise는 <future> 라이브러리의 std::future 및 std::promise 클래스로 표현됩니다. std::async는 비동기 함수를 시작하고 std::future를 반환합니다. std::promise는 set_value()를 통해 수동으로 값을 설정하거나 set_exception()을 통해 오류를 설정할 수 있습니다. Dart 및 JavaScript와 달리 std::future.get()은 결과를 받을 때까지 스레드를 차단합니다 — 이는 멀티스레드 C++ 애플리케이션을 설계할 때 고려해야 할 중요한 차이점입니다.
Future를 반환하는 API를 설계할 때는 단일 진실 공급원 규칙을 따르세요: 각 비동기 요청은 한 번 생성되고 Future.memoize를 통해 캐시됩니다. 두 구성 요소가 동일한 데이터를 요청하면 동일한 Future를 받습니다 — 이는 중복 요청을 방지합니다. 타임아웃의 경우 Future.timeout(Dart) 또는 타임아웃이 있는 Promise.race를 사용하여 비동기 작업이 지정된 간격 내에 완료되도록 보장합니다.
Future의 재시도 패턴은 재귀적 체인을 통해 구현됩니다: catch에서 오류가 발생하면 동일한 비동기 함수가 지연과 함께 호출됩니다. Exponential backoff는 시도 간격을 증가시킵니다: 1초 — 2초 — 4초 — 최대 30초까지. Dart에서는 Future의 확장 메서드에 캡슐화됩니다: future.retry(maxRetries: 3, delay: Duration(seconds: 1)). Scala에서 Future.zip은 두 개의 독립적인 결과를 튜플로 결합합니다 — 두 Future에 대한 Promise.all의 대안입니다. 표준 Future에서는 취소가 지원되지 않지만 JavaScript에서는 CancellationToken 또는 AbortController를 통해 구현됩니다.
자주 묻는 질문
Future는 then 또는 await를 통해 비동기 결과를 읽기 위한 읽기 전용 컨테이너입니다. Promise는 resolve()를 통해 값을 받아 Future에 연결하는 쓰기 전용 계약입니다. Dart와 JavaScript에서는 이러한 개념이 결합되어 있습니다: Future/Promise는 하나의 클래스입니다. Java와 Scala에서는 분리되어 있습니다.
Future는 단일 비동기 결과를 나타냅니다 — 정확히 하나의 값을 방출하고 완료됩니다. Observable(Rx)은 시간에 따라 여러 값을 방출합니다 — 비동기 스트림입니다. 하나의 HTTP 응답이 필요하면 — Future를 사용하세요. 데이터베이스 변경 스트림이 필요하면 — Observable 또는 Stream을 사용하세요.
async/await는 Future/Promise 위의 문법적 설탕입니다. async 키워드는 함수를 비동기로 표시합니다 — Future를 반환합니다. await는 스레드를 차단하지 않고 Future가 완료될 때까지 실행을 일시 중지합니다. Dart, JavaScript, Python 및 C#에서 async/await는 대부분의 시나리오에서 then 체인을 대체했습니다.
Dart에서는 체인 끝에 .catchError()를 사용합니다 — 모든 단계에서 예외를 포착합니다. JavaScript에서는 — .catch(). Dart는 async 함수 내에서 try/await/catch도 제공합니다. 오류 복구를 위해 기본값 또는 새 Future를 반환하여 .catchError()를 사용하세요.
Promise.all은 프로미스 배열을 받아 결과 배열로 해결되는 단일 Promise를 반환합니다. 모든 프로미스는 병렬로 실행됩니다 — 이는 독립적인 작업(예: 여러 파일 로드)을 가속화합니다. 프로미스가 하나라도 거부되면 — 전체 Promise.all이 동일한 오류로 거부됩니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.