EventLoopFuture

public final class EventLoopFuture<T>

Holder for a result that will be provided later.

Functions that promise to do work asynchronously can return an EventLoopFuture<T>. The recipient of such an object can then observe it to be notified when the operation completes.

The provider of a EventLoopFuture<T> can create and return a placeholder object before the actual result is available. For example:

func getNetworkData(args) -> EventLoopFuture<NetworkResponse> {
    let promise: EventLoopPromise<NetworkResponse> = eventLoop.newPromise()
    queue.async {
        . . . do some work . . .
        promise.succeed(response)
        . . . if it fails, instead . . .
        promise.fail(error)
    }
    return promise.futureResult
}

Note that this function returns immediately; the promise object will be given a value later on. This behaviour is common to Future/Promise implementations in many programming languages. If you are unfamiliar with this kind of object, the following resources may be helpful:

If you receive a EventLoopFuture<T> from another function, you have a number of options: The most common operation is to use then() or map() to add a function that will be called with the eventual result. Both methods returns a new EventLoopFuture<T> immediately that will receive the return value from your function, but they behave differently. If you have a function that can return synchronously, the map function will transform the result T to a the new result value U and return an EventLoopFuture<U>.

let networkData = getNetworkData(args)

// When network data is received, convert it.
let processedResult: EventLoopFuture<Processed> = networkData.map { (n: NetworkResponse) -> Processed in
    ... parse network data ....
    return processedResult
}

If however you need to do more asynchronous processing, you can call then(). The return value of the function passed to then must be a new EventLoopFuture<U> object: the return value of then() is a new EventLoopFuture<U> that will contain the eventual result of both the original operation and the subsequent one.

// When converted network data is available, begin the database operation.
let databaseResult: EventLoopFuture<DBResult> = processedResult.then { (p: Processed) -> EventLoopFuture<DBResult> in
    return someDatabaseOperation(p)
}

In essence, future chains created via then() provide a form of data-driven asynchronous programming that allows you to dynamically declare data dependencies for your various operations.

EventLoopFuture chains created via then() are sufficient for most purposes. All of the registered functions will eventually run in order. If one of those functions throws an error, that error will bypass the remaining functions. You can use thenIfError() to handle and optionally recover from errors in the middle of a chain.

At the end of an EventLoopFuture chain, you can use whenSuccess() or whenFailure() to add an observer callback that will be invoked with the result or error at that point. (Note: If you ever find yourself invoking promise.succeed() from inside a whenSuccess() callback, you probably should use then() or cascade(promise:) instead.)

EventLoopFuture objects are typically obtained by:

  • Using EventLoopFuture<T>.async or a similar wrapper function.
  • Using .then() on an existing future to create a new future for the next step in a series of operations.
  • Initializing an EventLoopFuture that already has a value or an error

Threading and Futures

One of the major performance advantages of NIO over something like Node.js or Python’s asyncio is that NIO will by default run multiple event loops at once, on different threads. As most network protocols do not require blocking operation, at least in their low level implementations, this provides enormous speedups on machines with many cores such as most modern servers.

However, it can present a challenge at higher levels of abstraction when coordination between those threads becomes necessary. This is usually the case whenever the events on one connection (that is, one Channel) depend on events on another one. As these Channels may be scheduled on different event loops (and so different threads) care needs to be taken to ensure that communication between the two loops is done in a thread-safe manner that avoids concurrent mutation of shared state from multiple loops at once.

The main primitives NIO provides for this use are the EventLoopPromise and EventLoopFuture. As their names suggest, these two objects are aware of event loops, and so can help manage the safety and correctness of your programs. However, understanding the exact semantics of these objects is critical to ensuring the safety of your code.

Callbacks

The most important principle of the EventLoopPromise and EventLoopFuture is this: all callbacks registered on an EventLoopFuture will execute on the thread corresponding to the event loop that created the Future, regardless of what thread succeeds or fails the corresponding EventLoopPromise.

This means that if your code created the EventLoopPromise, you can be extremely confident of what thread the callback will execute on: after all, you held the event loop in hand when you created the EventLoopPromise. However, if your code is handed an EventLoopFuture or EventLoopPromise, and you want to register callbacks on those objects, you cannot be confident that those callbacks will execute on the same EventLoop that your code does.

This presents a problem: how do you ensure thread-safety when registering callbacks on an arbitrary EventLoopFuture? The short answer is that when you are holding an EventLoopFuture, you can always obtain a new EventLoopFuture whose callbacks will execute on your event loop. You do this by calling EventLoopFuture.hopTo(eventLoop:). This function returns a new EventLoopFuture whose callbacks are guaranteed to fire on the provided event loop. As an added bonus, hopTo will check whether the provided EventLoopFuture was already scheduled to dispatch on the event loop in question, and avoid doing any work if that was the case.

This means that for any EventLoopFuture that your code did not create itself (via EventLoopPromise.futureResult), use of hopTo is strongly encouraged to help guarantee thread-safety. It should only be elided when thread-safety is provably not needed.

The thread affinity of EventLoopFutures is critical to writing safe, performant concurrent code without boilerplate. It allows you to avoid needing to write or use locks in your own code, instead using the natural synchronization of the EventLoop to manage your thread-safety. In general, if any of your ChannelHandlers or EventLoopFuture callbacks need to invoke a lock (either directly or in the form of DispatchQueue) this should be considered a code smell worth investigating: the EventLoop-based synchronization guarantees of EventLoopFuture should be sufficient to guarantee thread-safety.

  • The EventLoop which is tied to the EventLoopFuture and is used to notify all registered callbacks.

    Declaration

    Swift

    public let eventLoop: EventLoop
  • Declaration

    Swift

    public static func ==(lhs: EventLoopFuture, rhs: EventLoopFuture) -> Bool
  • When the current EventLoopFuture<T> is fulfilled, run the provided callback, which will provide a new EventLoopFuture.

    This allows you to dynamically dispatch new asynchronous tasks as phases in a longer series of processing steps. Note that you can use the results of the current EventLoopFuture<T> when determining how to dispatch the next operation.

    This works well when you have APIs that already know how to return EventLoopFutures. You can do something with the result of one and just return the next future:

    let d1 = networkRequest(args).future()
    let d2 = d1.then { t -> EventLoopFuture<U> in
        . . . something with t . . .
        return netWorkRequest(args)
    }
    d2.whenSuccess { u in
        NSLog("Result of second request: \(u)")
    }
    

    Note: In a sense, the EventLoopFuture<U> is returned before it’s created.

    Declaration

    Swift

    public func then<U>(file: StaticString = #file, line: UInt = #line, _ callback: @escaping (T) -> EventLoopFuture<U>) -> EventLoopFuture<U>

    Parameters

    callback

    Function that will receive the value of this EventLoopFuture and return a new EventLoopFuture.

    Return Value

    A future that will receive the eventual value.

  • When the current EventLoopFuture<T> is fulfilled, run the provided callback, which performs a synchronous computation and returns a new value of type U. The provided callback may optionally throw.

    Operations performed in thenThrowing should not block, or they will block the entire event loop. thenThrowing is intended for use when you have a data-driven function that performs a simple data transformation that can potentially error.

    If your callback function throws, the returned EventLoopFuture will error.

    Declaration

    Swift

    public func thenThrowing<U>(file: StaticString = #file, line: UInt = #line, _ callback: @escaping (T) throws -> U) -> EventLoopFuture<U>

    Parameters

    callback

    Function that will receive the value of this EventLoopFuture and return a new value lifted into a new EventLoopFuture.

    Return Value

    A future that will receive the eventual value.

  • When the current EventLoopFuture<T> is in an error state, run the provided callback, which may recover from the error and returns a new value of type U. The provided callback may optionally throw, in which case the EventLoopFuture will be in a failed state with the new thrown error.

    Operations performed in thenIfErrorThrowing should not block, or they will block the entire event loop. thenIfErrorThrowing is intended for use when you have the ability to synchronously recover from errors.

    If your callback function throws, the returned EventLoopFuture will error.

    Declaration

    Swift

    public func thenIfErrorThrowing(file: StaticString = #file, line: UInt = #line, _ callback: @escaping (Error) throws -> T) -> EventLoopFuture<T>

    Parameters

    callback

    Function that will receive the error value of this EventLoopFuture and return a new value lifted into a new EventLoopFuture.

    Return Value

    A future that will receive the eventual value or a rethrown error.

  • When the current EventLoopFuture<T> is fulfilled, run the provided callback, which performs a synchronous computation and returns a new value of type U.

    Operations performed in map should not block, or they will block the entire event loop. map is intended for use when you have a data-driven function that performs a simple data transformation that can potentially error.

    let future1 = eventually()
    let future2 = future1.map { T -> U in
        ... stuff ...
        return u
    }
    let future3 = future2.map { U -> V in
        ... stuff ...
        return v
    }
    

    Declaration

    Swift

    public func map<U>(file: StaticString = #file, line: UInt = #line, _ callback: @escaping (T) -> (U)) -> EventLoopFuture<U>

    Parameters

    callback

    Function that will receive the value of this EventLoopFuture and return a new value lifted into a new EventLoopFuture.

    Return Value

    A future that will receive the eventual value.

  • When the current EventLoopFuture<T> is in an error state, run the provided callback, which may recover from the error by returning an EventLoopFuture<U>. The callback is intended to potentially recover from the error by returning a new EventLoopFuture that will eventually contain the recovered result.

    If the callback cannot recover it should return a failed EventLoopFuture.

    Declaration

    Swift

    public func thenIfError(file: StaticString = #file, line: UInt = #line, _ callback: @escaping (Error) -> EventLoopFuture<T>) -> EventLoopFuture<T>

    Parameters

    callback

    Function that will receive the error value of this EventLoopFuture and return a new value lifted into a new EventLoopFuture.

    Return Value

    A future that will receive the recovered value.

  • When the current EventLoopFuture<T> is in an error state, run the provided callback, which can recover from the error and return a new value of type U. The provided callback may not throw, so this function should be used when the error is always recoverable.

    Operations performed in mapIfError should not block, or they will block the entire event loop. mapIfError is intended for use when you have the ability to synchronously recover from errors.

    Declaration

    Swift

    public func mapIfError(file: StaticString = #file, line: UInt = #line, _ callback: @escaping (Error) -> T) -> EventLoopFuture<T>

    Parameters

    callback

    Function that will receive the error value of this EventLoopFuture and return a new value lifted into a new EventLoopFuture.

    Return Value

    A future that will receive the recovered value.

  • Adds an observer callback to this EventLoopFuture that is called when the EventLoopFuture has a success result.

    An observer callback cannot return a value, meaning that this function cannot be chained from. If you are attempting to create a computation pipeline, consider map or then. If you find yourself passing the results from this EventLoopFuture to a new EventLoopPromise in the body of this function, consider using cascade instead.

    Declaration

    Swift

    public func whenSuccess(_ callback: @escaping (T) -> Void)

    Parameters

    callback

    The callback that is called with the successful result of the EventLoopFuture.

  • Adds an observer callback to this EventLoopFuture that is called when the EventLoopFuture has a failure result.

    An observer callback cannot return a value, meaning that this function cannot be chained from. If you are attempting to create a computation pipeline, consider mapIfError or thenIfError. If you find yourself passing the results from this EventLoopFuture to a new EventLoopPromise in the body of this function, consider using cascade instead.

    Declaration

    Swift

    public func whenFailure(_ callback: @escaping (Error) -> Void)

    Parameters

    callback

    The callback that is called with the failed result of the EventLoopFuture.

  • Adds an observer callback to this EventLoopFuture that is called when the EventLoopFuture has any result.

    Unlike its friends whenSuccess and whenFailure, whenComplete does not receive the result of the EventLoopFuture. This is because its primary purpose is to do the appropriate cleanup of any resources that needed to be kept open until the EventLoopFuture had resolved.

    Declaration

    Swift

    public func whenComplete(_ callback: @escaping () -> Void)

    Parameters

    callback

    The callback that is called when the EventLoopFuture is fulfilled.

  • Return a new EventLoopFuture that succeeds when this and another provided EventLoopFuture both succeed. It then provides the pair of results. If either one fails, the combined EventLoopFuture will fail with the first error encountered.

    Declaration

    Swift

    public func and<U>(_ other: EventLoopFuture<U>, file: StaticString = #file, line: UInt = #line) -> EventLoopFuture<(T, U)>
  • Return a new EventLoopFuture that contains this and another value. This is just syntactic sugar for future.and(loop.newSucceedFuture<U>(result: result)).

    Declaration

    Swift

    public func and<U>(result: U, file: StaticString = #file, line: UInt = #line) -> EventLoopFuture<(T, U)>
  • Fulfill the given EventLoopPromise with the results from this EventLoopFuture.

    This is useful when allowing users to provide promises for you to fulfill, but when you are calling functions that return their own promises. They allow you to tidy up your computational pipelines. For example:

    doWork().then {
        doMoreWork($0)
    }.then {
        doYetMoreWork($0)
    }.thenIfError {
        maybeRecoverFromError($0)
    }.map {
        transformData($0)
    }.cascade(promise: userPromise)
    

    Declaration

    Swift

    public func cascade(promise: EventLoopPromise<T>)

    Parameters

    promise

    The EventLoopPromise to fulfill with the results of this future.

  • Fulfill the given EventLoopPromise with the error result from this EventLoopFuture, if one exists.

    This is an alternative variant of cascade that allows you to potentially return early failures in error cases, while passing the user EventLoopPromise onwards. In general, however, cascade is more broadly useful.

    Declaration

    Swift

    public func cascadeFailure<U>(promise: EventLoopPromise<U>)

    Parameters

    promise

    The EventLoopPromise to fulfill with the results of this future.

  • Wait for the resolution of this EventLoopFuture by blocking the current thread until it resolves.

    If the EventLoopFuture resolves with a value, that value is returned from wait(). If the EventLoopFuture resolves with an error, that error will be thrown instead. wait() will block whatever thread it is called on, so it must not be called on event loop threads: it is primarily useful for testing, or for building interfaces between blocking and non-blocking code.

    Throws

    The error value of the EventLoopFuture if it errors.

    Declaration

    Swift

    public func wait(file: StaticString = #file, line: UInt = #line) throws -> T

    Return Value

    The value of the EventLoopFuture when it completes.

  • Returns a new EventLoopFuture that fires only when this EventLoopFuture and all the provided futures complete. It then provides the result of folding the value of this EventLoopFuture with the values of all the provided futures.

    This function is suited when you have APIs that already know how to return EventLoopFutures.

    The returned EventLoopFuture will fail as soon as the a failure is encountered in any of the futures (or in this one). However, the failure will not occur until all preceding EventLoopFutures have completed. At the point the failure is encountered, all subsequent EventLoopFuture objects will no longer be waited for. This function therefore fails fast: once a failure is encountered, it will immediately fail the overall EventLoopFuture.

    Declaration

    Swift

    public func fold<U>(_ futures: [EventLoopFuture<U>], with combiningFunction: @escaping (T, U) -> EventLoopFuture<T>) -> EventLoopFuture<T>

    Parameters

    futures

    An array of EventLoopFuture<U> to wait for.

    with

    A function that will be used to fold the values of two EventLoopFutures and return a new value wrapped in an EventLoopFuture.

    Return Value

    A new EventLoopFuture with the folded value whose callbacks run on self.eventLoop.

  • Returns a new EventLoopFuture that fires only when all the provided futures complete.

    This extension is only available when you have a collection of EventLoopFutures that do not provide result data: that is, they are completion notifiers. In this case, you can wait for all of them. The returned EventLoopFuture will fail as soon as any of the futures fails: otherwise, it will succeed only when all of them do.

    Declaration

    Swift

    public static func andAll(_ futures: [EventLoopFuture<Void>], eventLoop: EventLoop) -> EventLoopFuture<Void>

    Parameters

    futures

    An array of EventLoopFuture<Void> to wait for.

    eventLoop

    The EventLoop on which the new EventLoopFuture callbacks will fire.

    Return Value

    A new EventLoopFuture.

  • Returns a new EventLoopFuture that fires only when all the provided futures complete. The new EventLoopFuture contains the result of reducing the initialResult with the values of the [EventLoopFuture<U>].

    This function makes copies of the result for each EventLoopFuture, for a version which avoids making copies, check out reduce<U>(into:).

    The returned EventLoopFuture will fail as soon as a failure is encountered in any of the futures. However, the failure will not occur until all preceding EventLoopFutures have completed. At the point the failure is encountered, all subsequent EventLoopFuture objects will no longer be waited for. This function therefore fails fast: once a failure is encountered, it will immediately fail the overall EventLoopFuture.

    Declaration

    Swift

    public static func reduce<U>(_ initialResult: T, _ futures: [EventLoopFuture<U>], eventLoop: EventLoop, _ nextPartialResult: @escaping (T, U) -> T) -> EventLoopFuture<T>

    Parameters

    initialResult

    An initial result to begin the reduction.

    futures

    An array of EventLoopFuture to wait for.

    eventLoop

    The EventLoop on which the new EventLoopFuture callbacks will fire.

    nextPartialResult

    The bifunction used to produce partial results.

    Return Value

    A new EventLoopFuture with the reduced value.

  • Returns a new EventLoopFuture that fires only when all the provided futures complete. The new EventLoopFuture contains the result of combining the initialResult with the values of the [EventLoopFuture<U>]. This function is analogous to the standard library’s reduce(into:), which does not make copies of the result type for each EventLoopFuture.

    The returned EventLoopFuture will fail as soon as a failure is encountered in any of the futures. However, the failure will not occur until all preceding EventLoopFutures have completed. At the point the failure is encountered, all subsequent EventLoopFuture objects will no longer be waited for. This function therefore fails fast: once a failure is encountered, it will immediately fail the overall EventLoopFuture.

    Declaration

    Swift

    public static func reduce<U>(into initialResult: T, _ futures: [EventLoopFuture<U>], eventLoop: EventLoop, _ updateAccumulatingResult: @escaping (inout T, U) -> Void) -> EventLoopFuture<T>

    Parameters

    initialResult

    An initial result to begin the reduction.

    futures

    An array of EventLoopFuture to wait for.

    eventLoop

    The EventLoop on which the new EventLoopFuture callbacks will fire.

    updateAccumulatingResult

    The bifunction used to combine partialResults with new elements.

    Return Value

    A new EventLoopFuture with the combined value.

  • Returns an EventLoopFuture that fires when this future completes, but executes its callbacks on the target event loop instead of the original one.

    It is common to want to hop event loops when you arrange some work: for example, you’re closing one channel from another, and want to hop back when the close completes. This method lets you spell that requirement succinctly. It also contains an optimisation for the case when the loop you’re hopping from is the same as the one you’re hopping to, allowing you to avoid doing allocations in that case.

    Declaration

    Swift

    func hopTo(eventLoop target: EventLoop) -> EventLoopFuture<T>

    Parameters

    target

    The EventLoop that the returned EventLoopFuture will run on.

    Return Value

    An EventLoopFuture whose callbacks run on target instead of the original loop.