Fact-checked by Grok 2 weeks ago

Futures and promises

Futures and promises are programming abstractions used in concurrent and asynchronous computing to represent the eventual result of an operation that may not be immediately available, enabling the decoupling of computation initiation from its completion and facilitating efficient handling of parallelism without blocking the main execution flow. The concept of futures emerged in the late 1970s as a mechanism to manage parallel evaluation in applicative languages, with Henry G. Baker and Carl Hewitt introducing futures in their 1977 work on the incremental garbage collection of processes, where futures served as placeholders for values computed by concurrent processes. Building on this, Daniel P. Friedman and David S. Wise coined the term "promise" in 1978 to describe a similar construct in applicative programming for parallel processing, emphasizing promises as commitments to deliver a value upon demand, which could be fulfilled asynchronously. These ideas gained practical traction with Robert H. Halstead's 1985 implementation of futures in Multilisp, a parallel extension of the Scheme programming language, where the future primitive allowed immediate returns of future objects while spawning parallel evaluations. In contemporary programming, futures and promises have evolved into versatile tools for managing concurrency across diverse paradigms. They support operations like (e.g., then or flatMap), error handling, and cancellation, making them essential for scalable applications in distributed systems and . Languages such as provide immutable futures as read-only views of promises, ensuring thread-safe access and . Similarly, Java's CompletableFuture class, introduced in Java 8, extends these concepts to enable asynchronous pipelines with functional-style transformations. Their adoption underscores a shift toward non-blocking, event-driven architectures that enhance performance in multicore and networked environments.

Fundamentals

Definition and Overview

In , a future is an object that acts as a for the eventual result of an asynchronous computation, allowing a to continue execution without blocking while the computation proceeds in parallel or on another . A promise, in contrast, represents the writable counterpart to a future, providing a mechanism for the producer of the computation to assign a value (or an ) to the future once the operation completes. This duality enables and communication between concurrent parts of a , where the future serves as the read-only for consumers awaiting the result. The primary motivation for futures and promises arises from the need to manage concurrency, non-blocking I/O, and parallelism in systems where operations like network requests, file reads, or complex calculations may introduce significant . By the initiation of a task from its completion, these constructs prevent program halting, allowing threads or event loops to handle multiple tasks efficiently and improving overall system responsiveness. For instance, in distributed systems, they facilitate asynchronous procedure calls, enabling callers to proceed immediately while results are computed remotely. The basic lifecycle of a future begins with its creation, typically by spawning an asynchronous task that binds to the future; it remains in a pending until the computation resolves successfully with a value or fails with an exception. Resolution updates the future's , after which consumers can access the result through methods like blocking get() (which waits if necessary) or non-blocking await (which may use callbacks or ). Key benefits include enhanced responsiveness by avoiding freezes during I/O, greater server scalability through concurrent request handling, and easier of asynchronous tasks via or pipelining. A simple example illustrates and resolution:
future = createFuture()
spawnAsyncTask(computeValue) { result ->
    resolve(future, result)  // or reject(future, error)
}
value = get(future)  // Blocks until resolved
This , adapted from foundational concurrent models, underscores how futures enable non-blocking parallelism.

Implicit vs. Explicit

In implicit futures and promises, the language runtime automatically generates the future object upon of an asynchronous , eliminating the need for upfront explicit by the . This approach integrates seamlessly with the language's concurrency model, where the future serves as a transparent for the eventual value. For instance, in , declaring an async function implicitly returns a without requiring manual instantiation, allowing the runtime to handle the asynchronous execution and resolution. Explicit futures, by contrast, demand that the programmer manually instantiate the future object before commencing the , providing a visible handle for managing the asynchronous task. In , this is typically done via the Future.apply method, which accepts a code block representing the and schedules it on an execution context, returning a Future[T] immediately. This explicit step ensures the developer has direct oversight from the outset. The choice between implicit and explicit mechanisms involves key trade-offs in usability and . Implicit creation minimizes boilerplate and enhances code by hiding the machinery of asynchrony, but it can obscure points, fulfillment stages, and resource overheads, potentially leading to unintended blocking or inefficient resource use. Explicit futures mitigate these issues by offering precise over scheduling, propagation, and , though at the cost of increased and type complexity in the code. To illustrate, consider the following examples: Implicit (e.g., JavaScript-style async function):
async function fetchData() {
    const result = await apiCall();  // Runtime creates [Promise](/page/Promise) implicitly
    return process(result);
}
// Caller receives [Promise](/page/Promise) without explicit future handling
Explicit (e.g., -style future creation):
import scala.concurrent.[Future](/page/Future)
val [future](/page/Future) = [Future](/page/Future).apply {  // [Programmer](/page/Programmer) creates [Future](/page/Future) explicitly
    val result = apiCall()
    process(result)
}
// [Computation](/page/Computation) starts asynchronously; [future](/page/Future) holds the result

Variations and Features

Promise Pipelining

Promise pipelining is a technique for composing asynchronous operations by sequencing promises, where the resolution of one promise serves as the input to the next, allowing for efficient chaining without intermediate blocking. Introduced as "call-streams" in the system, this mechanism enables a caller to issue a series of remote procedure calls in a , deferring resolution until results are needed, thereby supporting concurrency and reducing in distributed environments. In modern implementations, such as those in JavaScript's standard, pipelining manifests through methods like .then(), which return new promises that link operations sequentially. The primary benefits of promise pipelining include improved code readability by avoiding deeply nested callbacks—often termed "callback hell"—and seamless error propagation across the chain, where rejections in any step can be caught uniformly at the end or intermediate points. This approach facilitates dataflow-style programming, where operations proceed as dependencies resolve, minimizing round-trip delays in networked systems. For instance, in , pipelining allows multiple messages to be sent before prior responses arrive, optimizing throughput. Mechanically, pipelining distinguishes between transformation operations like map (which applies a to the resolved and wraps it in a new ) and flatMap (which unwraps the inner from the transformation result to continue seamlessly). Success paths forward the resolved through the sequence, while failure paths propagate exceptions or rejections, often short-circuiting unless handled. In call-stream model, ensure ordered delivery of calls and replies, with s acting as placeholders that block only upon explicit claiming. Error handling integrates exception , allowing a single catch to manage failures from any linked operation, though unhandled rejections may lead to silent failures if not configured properly. Consider a example of chaining asynchronous operations, such as fetching, processing, and storing data:
let dataPromise = fetchData();  // Asynchronous fetch returning a [promise](/page/Promise)
let processedPromise = dataPromise.then(processData);  // [Apply](/page/Apply) transformation, returns new [promise](/page/Promise)
let storePromise = processedPromise.then(storeData);  // Chain storage, handles success/failure from prior
storePromise.catch(handleError);  // Unified error handling for the [pipeline](/page/Pipeline)
This sequence executes non-blockingly, with each .then() creating a dependent that resolves only after the previous one, enabling overlap in distributed settings. Despite these advantages, promise pipelining has limitations, including the risk of in implementations with deep recursive chaining if not optimized for calls, and challenges in rejected promises that propagate unexpectedly through long chains. In distributed contexts, network partitions can disrupt , requiring recovery mechanisms like restarts, while composing dynamic numbers of operations may introduce complexity in ensuring proper termination.

Read-only Views

Read-only views in the context of futures and promises provide a mechanism for accessing the and eventual of an underlying without permitting modifications to it, treating the future as an immutable that represents a computation's outcome, allowing consumers to query readiness or retrieve results once available. The core purpose of these views is to facilitate safe sharing of asynchronous results across multiple threads or program modules in concurrent settings, thereby avoiding race conditions associated with direct access to the writable . By enforcing read-only access, they ensure that only the designated producer can resolve the , while multiple observers can independently or consume it without interfering with the shared . In implementation, read-only views typically expose query methods such as get() for value retrieval and isDone() or wait() for status checks, while deliberately omitting mutation operations like set() or complete(). This interface design promotes through internal on the shared state, often using operations or locks to handle concurrent queries. A representative example appears in C++'s , where std::[future](/page/Future) offers a single-use read-only tied to exclusive , suitable for producer-consumer scenarios, whereas std::shared_future extends this to multiple concurrent accesses via copyable references, at the cost of minor performance overhead from on the shared state. Such views find application in parallel frameworks, where they enable the distribution of computation results to numerous worker threads without ownership transfer, supporting efficient result broadcasting in task-parallel systems like those using directed acyclic graphs of dependencies.

Thread-specific Futures

Thread-specific futures are asynchronous computation constructs bound to a particular thread, scheduler, or executor, ensuring that the associated computation executes within a designated execution context rather than migrating across arbitrary threads. This binding enforces locality, where the future's value is produced by a thread dedicated to that task, often representing the thread's lifecycle directly. For instance, in languages like Alice ML, futures are explicitly tied to specific threads, with computations initiating either eagerly upon creation or lazily upon access, allowing the thread to handle the evaluation in isolation. The primary advantages of thread-specific futures include reduced overhead from context switching, as the computation remains confined to its assigned thread, minimizing synchronization costs when accessing thread-local storage or resources. This design simplifies debugging by localizing execution traces to a single thread, making it easier to monitor and profile without tracing cross-thread interactions. Additionally, it supports efficient resource management, such as direct thread interruption for cancellation, which halts the computation and reclaims resources more predictably than in thread-agnostic models. In contrast to thread-agnostic futures, which allow flexible scheduling across any available thread, thread-specific variants prioritize predictability and efficiency in environments with constrained threading models. Examples of thread-specific futures appear in several languages and libraries. In Java, the CompletableFuture class can be configured with a custom Executor, such as a single-threaded executor, to bind the computation to a specific thread or pool, ensuring execution context locality for tasks like I/O-bound operations. Similarly, Scala's libraries like cats-effect and ZIO use "fibers" as lightweight, thread-specific futures that tie computations to managed threads, enabling composable asynchronous workflows while maintaining isolation. These differ from more general promise-futures, where the producing thread is decoupled from the consuming one, potentially requiring additional messaging for resolution. Despite these benefits, thread-specific futures introduce challenges, such as potential performance bottlenecks when overused on a limited number of threads, leading to of tasks that could otherwise parallelize across a . Migration to broader thread pools may require refactoring to detach futures from strict bindings, complicating scalability in dynamic workloads. In relation to the , thread-specific futures provide a lightweight form of similar to actor threads, confining state mutations without full message-passing semantics, though they lack inherent actor guarantees like .

Semantics and Behavior

Blocking vs Non-blocking Semantics

In futures and promises, blocking semantics refer to operations where the calling suspends execution until the future's is available, ensuring synchronous access to the result. This approach, exemplified by methods like get() in early futures, ties the thread's progress directly to the completion of the asynchronous computation, potentially leading to resource inefficiency if the underlying task involves prolonged waits such as I/O operations. Blocking is particularly straightforward in single-threaded or sequential contexts but can propagate delays across the system. Non-blocking semantics, in contrast, allow the calling to continue execution without suspension, typically through mechanisms like polling the 's state or registering callbacks to handle completion asynchronously. For instance, a might support an onComplete handler that invokes a provided only when the result is ready, preserving availability for other tasks and enhancing concurrency in multi-threaded environments. This model aligns with event-driven architectures, where selectors or reactors manage multiple futures without dedicated threads per operation, promoting scalability in high-throughput systems like web servers. The trade-offs between these semantics are significant: blocking simplifies program flow by mimicking synchronous code, reducing the on developers, but it risks deadlocks—especially in systems with limited threads—and underutilizes CPU resources during waits. Non-blocking avoids such issues, enabling better and , yet it introduces in managing asynchronous flows, such as chaining callbacks or handling exceptions across non-linear execution paths. These concerns often tie into broader evaluation strategies, where blocking may occur only upon explicit demand from the consumer. To illustrate, consider pseudocode for a blocking approach in a simple loop awaiting multiple futures:
Future<String> f1 = computeAsync("task1");
Future<String> f2 = computeAsync("task2");
String result1 = f1.get();  // Blocks until resolved
String result2 = f2.get();  // Blocks until resolved
process(result1 + result2);
In a non-blocking variant using callbacks:
Future<String> f1 = computeAsync("task1");
Future<String> f2 = computeAsync("task2");
f1.onComplete(value1 -> {
    if (f2.isCompleted()) {
        process(value1 + f2.get());  // Non-blocking check
    }
});
f2.onComplete(value2 -> {
    if (f1.isCompleted()) {
        process(f1.get() + value2);
    }
});
Hybrid approaches mitigate these extremes by incorporating timeouts in blocking calls, such as get(timeout), which suspends only up to a specified duration before yielding control or throwing an exception, or cooperative yielding where the thread periodically checks progress without full suspension. These methods balance simplicity with non-blocking benefits, though they require careful tuning to avoid indefinite waits or excessive polling overhead.

Evaluation Strategy

Futures and promises employ various evaluation strategies that determine when the underlying computation begins, influencing resource utilization and parallelism. These strategies primarily contrast lazy and eager evaluation, with ties to broader paradigms like strict and non-strict evaluation in functional programming. In lazy evaluation, the computation associated with a future is deferred until its value is explicitly demanded, such as through a retrieval operation like get(). This approach conserves computational resources by avoiding execution of futures whose results are never accessed, thereby reducing unnecessary CPU cycles and memory allocation for discarded computations. For instance, in the R programming language's future package, lazy futures are created with the %lazy% TRUE directive, where evaluation only occurs upon value resolution, freezing globals at creation to maintain consistency without immediate execution. Conversely, eager evaluation initiates the immediately upon the future's creation, often in a separate or to enable early parallelism. This strategy suits scenarios where the result is likely needed, as it overlaps with other activities, potentially improving overall throughput in concurrent settings. In Java's CompletableFuture, methods like supplyAsync start asynchronous execution right away using the default ForkJoinPool, promoting immediate progress on the task without waiting for demand. The distinction between strict and non-strict evaluation further contextualizes these strategies, particularly in functional languages. Strict evaluation, akin to eager, requires arguments to be fully computed before , while non-strict evaluation delays this until necessity arises. Futures facilitate call-by-need—a form of non-strict evaluation—in such languages by using thunks to memoize results, avoiding redundant computations once evaluated, which enhances efficiency in lazy contexts like . In , parallel strategies via the parallel package leverage the language's inherent , sparking computations with rpar for parallelism while deferring full sparking until demanded, ensuring composable and deterministic behavior. These strategies impact memory usage, as lazy approaches minimize allocation for unused futures; CPU efficiency, where eager starts parallel work sooner but risks waste; and composability, enabling modular chaining of futures in parallel environments without premature evaluation. For example, Haskell's lazy futures support call-by-need for resource-efficient , contrasting with Java's eager CompletableFuture designed for immediate asynchronous dispatch.

Semantics in the Actor Model

In the actor model, serve as the fundamental units of , operating in and communicating exclusively through asynchronous , where futures embody the of a future reply from a recipient . This design ensures that maintain independence, avoiding shared state and enabling scalable concurrency without locks or direct . Futures, originally introduced as specialized for parallel execution, allow computations to proceed concurrently while deferring access to results until they become available. Within systems, integrate seamlessly with message-passing primitives, distinguishing between fire-and-forget sends using the ! , which dispatches messages without awaiting responses, and request-reply patterns using the ? or !? (often termed "ask"), which returns a representing the anticipated reply. In the ask pattern, the sending provides a —essentially the writable counterpart to the —as the reply destination, allowing the recipient to complete it with a value or exception via sender() ! reply or [Status](/page/Status).Failure(e). This duality of and facilitates non-blocking interactions, where the acts as a read-only for the pending result. The incorporation of futures enhances fault tolerance and scalability in distributed actor systems, as exemplified by the Akka framework, where supervision hierarchies propagate failures from unresolved or errored futures, enabling automatic restarts and location transparency across nodes. However, challenges arise in managing timeouts, where unfulfilled asks trigger AskTimeoutException after a specified duration (e.g., 5 seconds), and in supervision trees, which require explicit strategies to escalate or contain failures from incomplete futures without cascading system-wide disruptions. For instance, the ask pattern in might appear as follows, yielding a for the reply:
implicit val timeout = 5.seconds
val [future](/page/Future) = actor ? RequestMessage
[future](/page/Future).onComplete {
  case [Success](/page/Success)(result) => process(result)
  case Failure(error) => handleError(error)
}
Here, the actor responds by completing the implicit tied to the . In actor threads, futures may bind thread-specifically to ensure sequential access within the actor's processing.

Applications and Expressiveness

Applications

Futures and promises find extensive use in , where they facilitate asynchronous HTTP requests without blocking the main thread, enabling efficient handling of dynamic content loading. For instance, in , the Promise API allows developers to initiate network requests via or Fetch API, resolving with the response data upon completion or rejecting on errors, which supports seamless integration in both and server-side environments. This non-blocking behavior is particularly beneficial for server-side rendering in , where promises ensure that rendering tasks proceed concurrently with I/O operations, preventing delays in response times for user requests. In programming, futures and promises maintain responsiveness by offloading computationally intensive tasks, such as image processing, to background s or processes. Python's concurrent.futures module, for example, provides ThreadPoolExecutor and ProcessPoolExecutor classes that submit tasks like applying filters to images, returning objects to retrieve results without freezing the thread in frameworks like or . This approach ensures that user interactions, such as button clicks or window resizing, remain fluid even during prolonged operations, enhancing overall application . For in frameworks, futures enable parallel execution of map-reduce style tasks across distributed clusters, scaling computations beyond single-machine limits. Dask, a flexible for , leverages its futures interface—extending concurrent.futures—to submit functions for mapping over large datasets, such as transforming terabyte-scale arrays, and gathering results asynchronously for aggregation in reduce phases. This parallelism reduces processing times for tasks like data cleaning or statistical analysis, making it suitable for environments handling massive volumes without exhaustive memory usage. In distributed systems, futures and promises coordinate asynchronous calls among microservices, allowing services to communicate without synchronous waiting that could propagate failures. The Akka toolkit in Scala utilizes futures to pipe results between actors in a distributed actor model, enabling resilient orchestration of microservices for tasks like event sourcing or request aggregation across nodes. By completing futures with values from remote invocations, systems achieve fault-tolerant coordination, where timeouts and retries handle network latencies inherent in microservice architectures. Performance case studies demonstrate significant throughput gains from futures and promises in event-driven environments like . In one optimization effort for a billing engine processing hundreds of thousands of records, refactoring sequential fetches to use .all enabled parallel execution, reducing runtime from over five hours to under five minutes by minimizing idle wait times on independent async operations. Such improvements highlight how promises enhance efficiency, boosting overall system throughput in high-concurrency scenarios without altering core logic.

Relations between Expressiveness of Different Forms

Futures and promises can be taxonomized based on several dimensions that influence their computational expressiveness. Single-future variants, where a future represents a single pending value, are common in languages like and , allowing straightforward for individual asynchronous operations. In contrast, multi-future constructs, such as those in C# Tasks or 's Promise.all, enable aggregation of multiple pending results, supporting more complex parallel compositions but introducing challenges in error propagation across the set. Cancellable futures, exemplified by C#'s CancellationToken-integrated Tasks, permit explicit termination of computations to manage resources, enhancing expressiveness for long-running or speculative tasks, whereas non-cancellable forms like Promises lack this, limiting their utility in dynamic environments where operations may need interruption. Composite futures, including pipelined or chained variants, further extend this by allowing sequential or parallel assembly of operations, as seen in E's promise pipelining or Scala's flatMap on Futures. An expressiveness hierarchy emerges among these forms, with explicit futures providing greater control over synchronization points compared to implicit ones. Implicit futures, such as those in Alice ML or MultiLisp delays, automatically resolve upon access, promoting transparent integration into sequential code but obscuring and complicating of nested dependencies. Explicit futures, requiring deliberate operations like get() in , offer finer-grained control, enabling programmers to observe and manage computation stages, which is crucial for optimizing resource usage in concurrent settings. Pipelining enhances compositionality across both, allowing asynchronous operations to be linked without blocking, as in Scala's chains, which build on implicit resolution while adding explicit chaining for scalable async workflows; this elevates expressiveness by reducing callback proliferation and supporting declarative pipelines over imperative sequencing. Formally, futures and promises exhibit mutual capabilities, though with trade-offs in efficiency and feature support. Promises, as writable single-assignment containers, can simulate read-only futures by restricting fulfillment to once, as in Scala's Promise completing a paired Future; conversely, futures can emulate promises through callback registration on , effectively allowing deferred writes via event-driven completion. These encodings often rely on boxing mechanisms: data-flow (implicit) futures can be implemented atop control-flow (explicit) ones by wrapping values in future containers, and vice versa, preserving semantics but potentially incurring runtime overhead from additional indirection. Limitations arise in handling exceptions and cancellations; for instance, non-cancellable futures struggle to propagate interruptions uniformly in composites, leading to partial failures without coordinated cleanup, while in pipelined promises may require explicit try-catch chaining to avoid swallowing errors in asynchronous flows. Theoretically, futures and promises connect to in , modeling asynchronous composition as a monadic structure for sequencing effects. In , form a monad under flatMap () and map (fmap), enabling pure functional composition of async computations akin to the , where wraps values into pending states and chains resolutions while handling failures via recover. This monadic view abstracts away low-level concurrency details, allowing expressive definitions of complex workflows, such as parallel map-reduce patterns, through lawful composition that preserves . Despite these strengths, gaps persist in what futures and promises can express without supplementary primitives. They inherently model asynchrony and but cannot natively achieve true parallelism for CPU-bound tasks in single-threaded environments, such as JavaScript's , where futures simulate concurrency via non-blocking I/O but serialize execution, necessitating threads or processes for genuine multi-core utilization. Similarly, distributed expressiveness, like in models, requires additional messaging semantics beyond basic futures to handle network latencies and failures reliably. Coroutines represent a form of where functions can suspend and resume execution explicitly, often through points, enabling without the need for explicit result placeholders like futures. Unlike futures, which focus on representing the eventual result of an asynchronous and producer from consumer until resolution, coroutines emphasize suspension for ongoing tasks, such as in generators or async functions. This distinction makes coroutines suitable for scenarios requiring fine-grained interleaving of routines, whereas futures prioritize non-blocking result retrieval in event-driven systems. Channels serve as buffered or unbuffered queues facilitating communication between concurrent producers and consumers in a many-to-many or point-to-multipoint manner, enabling flows with built-in . In contrast to futures, which handle point-to-point delivery of a single result from an asynchronous operation, channels support ongoing producer-consumer interactions, such as pipelining multiple values without blocking on individual completions. For instance, channels are ideal for decoupling components in distributed systems where data arrives continuously, while futures excel in one-off computations like remote calls. Observables, as defined in reactive streams, model asynchronous data streams that can emit zero or more values over time, supporting operators for , filtering, and backpressure to manage producer-consumer rates. This extends beyond futures and promises, which typically resolve to a single value or , by allowing subscription to potentially infinite sequences with cancellation support. Tasks in task-based parallelism abstract units of work scheduled for execution, often returning futures to completion, but emphasize workload distribution across threads or processes rather than pure asynchrony. Continuations, meanwhile, capture the state of a computation to resume it later, providing a lower-level for composing asynchronous flows that futures build upon by implicitly chaining handlers. Futures suit one-shot asynchronous results, such as computing a value in the background, while channels and observables better handle streaming or multi-value scenarios like event processing pipelines, and coroutines or tasks fit iterative or parallel control flows requiring suspension points. Actor model messaging resembles channels in enabling decoupled communication but focuses on isolated state management across actors. Hybrids like async s combine coroutines' suspension with promise-like yielding of multiple values, allowing iterable asynchronous sequences that bridge generator simplicity and resolution. These constructs overlap in enabling non-blocking code but differ in : async generators facilitate lazy, on-demand production akin to coroutines, yet integrate for error propagation and completion.

Implementations by Language and Library

Futures and promises have been integrated into numerous programming languages as built-in features or through standard libraries, enabling asynchronous programming in diverse ecosystems. These implementations vary in their support for operations, error handling, and with concurrency models, often building on the core concepts of deferred and value resolution. Early adoptions focused on callback-based or polling mechanisms, while modern ones emphasize and like async/await. In , the Promises/A+ specification defines promises as objects representing the eventual completion or failure of an asynchronous operation, with a standardized then method for chaining handlers. This became a native feature in 2015, promoting interoperability across libraries without native cancellation support. Java's CompletableFuture, introduced in Java 8, extends the Future interface to allow explicit completion and acts as a CompletionStage for functional-style composition via methods like thenApply and handle. It supports cancellation through cancel and exception propagation. Python's asyncio module provides Future as an awaitable object for representing asynchronous results, integrated with the event loop for non-blocking I/O; it is not thread-safe and supports cancellation via cancel(). Scala's scala.concurrent.Future serves as a placeholder for a value computed asynchronously, often created via Future.apply and composed with flatMap or onComplete, relying on an ExecutionContext for scheduling but lacking built-in cancellation. In C++, std::future from the <future> header accesses results of asynchronous operations launched via std::async or std::packaged_task, using get() to block and retrieve values, with no direct cancellation but support for shared states via std::shared_future.
LanguageImplementationKey FeaturesCancellability
JavaScriptNative PromiseChaining with then, rejection handlingNo
JavaCompletableFutureFunctional composition, explicit completionYes
Pythonasyncio.FutureAwaitable, event loop integrationYes
Scalascala.concurrent.FutureMonadic composition, callbacksNo
C++std::futurePolling with wait(), shared stateNo
Beyond built-ins, libraries extend futures and promises for specific concurrency paradigms. Rust's asynchronous programming builds on the Future trait from the , where async blocks and functions generate futures polled by executors like ; the await keyword simplifies usage without altering the underlying future mechanics. Go lacks native futures but achieves similar semantics through goroutines and , where a channel acts as a single-value future by sending results asynchronously, as recommended for request-response patterns in the effective Go guide. C#'s Task and Task<T> , part of the Task-based Asynchronous Pattern (TAP) introduced in .NET 4.5 around 2012, underpin async and await for non-blocking code, supporting cancellation via CancellationToken and continuation with ContinueWith. In the 2020s, WebAssembly's interfaces have trended toward -based async operations, such as WebAssembly.instantiate returning a for module loading, aligning with APIs for efficient wasm execution. Library implementations often target domain-specific needs like networking or . Boost.Asio in C++ uses boost::asio::use_future as a completion token to return std::future from operations, enabling integration with concurrency. Twisted, a event-driven framework, employs Deferred objects—promise-like structures for asynchronous callbacks and errbacks—to manage deferred execution in network protocols. Akka, built on , leverages the standard within its , allowing futures to represent messages or computations dispatched to for distributed resilience.
LibraryLanguageImplementationKey Features
Boost.AsioC++use_futureAsync I/O to std::future
TwistedPythonDeferredCallback chaining for events
AkkaScalaFuture (extended)Actor-integrated async operations