PromiseKit

repository·master·Indexed 11 days ago

https://github.com/mxcl/promisekit

A comprehensive implementation of promises for Swift designed to simplify asynchronous programming across iOS, macOS, tvOS, watchOS, and Linux. PromiseKit 8 provides a readable syntax for chaining asynchronous tasks and includes specialized extensions for Apple frameworks such as Foundation, UIKit, MapKit, and CoreLocation.

Tokens
14.3K
Snippets
61
Records
78
Agent score
95%

What's inside PromiseKit

  1. Execute code regardless of outcome with `ensure` and `finally`

    master

    To perform cleanup or reset state (like hiding a loading spinner) regardless of whether a chain succeeds or fails, use these handlers:

    • ensure: This handler is always called, whether the chain succeeds or fails. It is part of the chain and can be followed by other handlers.
    • finally: A variant of ensure that terminates the promise chain and does not return a value.
    firstly {
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
        return login()
    }.then {
        fetch(avatar: $0.user)
    }.done {
        self.imageView = $0
    }.ensure {
        UIApplication.shared.isNetworkActivityIndicatorVisible = false
    }.catch {
        //…
    }
  2. Use `Guarantee<T>` for operations that cannot fail

    master

    Guarantee<T> is a specialized version of Promise used for asynchronous tasks that are guaranteed to succeed. Because they cannot fail, they do not support catch blocks. Use Guarantee instead of Promise whenever an operation is infallible to avoid unnecessary error-handling boilerplate.

    // Example of a Guarantee
    firstly {
        after(seconds: 0.1)
    }.done {
        // there is no way to add a `catch` because after cannot fail.
    }
    
    // Creating a Guarantee
    func fetch() -> Guarantee<String> {
        return Guarantee { seal in
            fetch { result in
                seal(result)
            }
        }
    }
  3. Manage retain cycles and memory in promise chains

    master

    Generally, you do not need to worry about retain cycles because once a promise completes, all handlers are released, which in turn releases any references to self.

    When to use weak self: Use weak self if your chain contains side effects that should not occur if a view controller is popped (e.g., modifying global application state, UserDefaults, or a database). You do not need to protect against changing the display state of a view controller (e.g., updating a UILabel).

    Retaining promises: You do not need to retain the promise itself. Every handler retains its promise until the handler is executed. Once all handlers have executed, the promise is deallocated. You only need to retain a promise if you need to access its final value after the chain has completed.

  4. How branched chains work

    master

    A branched chain occurs when you call .then multiple times on the same promise. When the parent promise resolves, both branches receive the value. However, the branches are entirely separate, and Swift will require each branch to have its own catch handler.

    To safely recombine branches, use when(fulfilled: ...) to wait for multiple promises to complete before handling the result in a single chain.

    let p1 = promise.then {
        // branch A
    }
    
    let p2 = promise.then {
        // branch B
    }
    
    when(fulfilled: p1, p2).catch { error in
        // handles errors from both branches
    }
  5. Handle Optionals in Promises using errors or compactMap

    master

    Avoid using Promise<Item?> (promises that resolve to an optional), as this forces consumers to handle nil on the happy path.

    Instead:

    1. Use the error path: If a condition (like an empty list) is an exceptional case, throw an error within a .map or .then block to move the logic to the .catch block.
    2. Use compactMap: If an external API returns an optional and you want to convert a nil result into a promise error, use compactMap.
    // Better: Shunt empty states to the error path
    return firstly {
        getItems()
    }.map { items -> [Item] in
        guard !items.isEmpty else {
            throw MyError.emptyItems
        }
        return items
    }
  6. Improve readability with `firstly`

    master

    While not strictly required, firstly is syntactic sugar used to start a promise chain. It makes the beginning of a chain more readable by clearly marking the entry point.

    Instead of calling a method that returns a promise directly: login().then { ... }

    You can use: firstly { login() }.then { ... }

  7. Recover from errors in a chain

    master

    In PromiseKit, there is a distinction between ending a chain and attempting to continue it after an error:

    • catch: Ends the chain and handles the error.
    • recover: Attempts to recover from an error to continue the chain.

    If you want to handle an error and return a new value to keep the chain going (similar to JavaScript's catch behavior), use recover instead of catch.

  8. Handle errors in a promise chain with `catch`

    master

    Errors in PromiseKit cascade down the chain. If any promise in a chain becomes rejected, all subsequent then blocks are skipped, and the execution jumps to the first available catch block. This ensures that errors are not silently ignored.

    firstly {
        login()
    }.then { creds in
        fetch(avatar: creds.user)
    }.done { image in
        self.imageView = image
    }.catch {
        // any errors in the whole chain land here
    }
  9. How to use Git Submodules for PromiseKit Extensions

    master

    If you want to avoid importing all PromiseKit extension frameworks (which can increase startup time and forced dependencies on Apple frameworks), you can import only CorePromise and add specific extensions via Git submodules.

    1. Use PromiseKit/CorePromise in your dependency manager.
    2. Initialize and add the specific extension submodule (e.g., UIKit).
    3. Add the submodule sources to your Xcode targets on a per-target basis.
    4. When updating, ensure you update both pods and submodules.
    # CocoaPods setup for Core only
    pod "PromiseKit/CorePromise"
    # Adding a specific extension submodule
    git submodule init
    git submodule add https://github.com/PromiseKit/UIKit Submodules/PMKUIKit
    
    # Updating everything
    pod update && git submodule update --recursive --remote
  10. Handle individual promise errors using recover

    master
    Instead of using when(resolved:) to ignore errors, the recommended pattern for continuing a chain even if one promise fails is to use .recover on the specific promise that might fail. This allows you to provide a fallback value or a different error handling strategy, keeping the chain as a standard Promise that can still use .catch.
  11. Understand when a promise body executes

    master

    The promise body executes immediately during the initialization of the promise on the current thread. If the body contains asynchronous tasks (like DispatchQueue.main.asyncAfter), those tasks will follow their own timing, but the initial setup code runs synchronously upon creation.

    let testPromise = Promise<Bool> { seal in
        print("Executing the promise body.")
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
            print("Executing asyncAfter.")
            return seal.fulfill(true)
        }
    }
  12. Understand Swift closure inference in PromiseKit

    master

    Swift can often infer return types for one-line closures in PromiseKit chains. For example, foo.then { bar($0) } is equivalent to explicitly defining the return type: foo.then { baz -> Promise<String> in return bar(baz) }.

    Warning: If the Swift compiler fails to infer the correct return type, you may need to provide explicit type annotations or consult the Troubleshooting Guide.