Chaining Promises

The true power of Then lies in its ability to chain asynchronous operations together in a clean, readable, and linear fashion. This avoids the nested structure of traditional callbacks.

.then()

The .then() method is the workhorse of promise chaining. It is executed when the preceding promise in the chain fulfills successfully. It takes the result of the previous promise as its input.

A .then() block can return two things:

  1. A new value: This value will be passed to the next .then() in the chain, wrapped in a new Promise.
  2. A new Promise: The chain will pause and wait for this new promise to resolve. Its result will then be passed to the next .then().

Example: Returning a Value

Here, the first .then returns a String, which is immediately passed to the second .then.

fetchUserId() // Returns Promise<Int>
    .then { userId -> String in
        return "User ID is \(userId)" // Return a String
    }
    .then { message in
        print(message) // Prints "User ID is 1234"
    }

Example: Returning a New Promise

This is the most common pattern, where you chain multiple asynchronous operations.

func fetchUserId() -> Promise<Int> { /* ... */ }
func fetchUserName(fromId id: Int) -> Promise<String> { /* ... */ }

fetchUserId() // Returns Promise<Int>
    .then { userId -> Promise<String> in
        // Return a new Promise
        return fetchUserName(fromId: userId)
    }
    .then { userName in
        // This is called only after fetchUserName resolves
        print("Username is \(userName)")
    }

Then provides a shorthand for this pattern by allowing you to pass the function reference directly:

fetchUserId()
    .then(fetchUserName) // Clean and readable
    .then { userName in
        print("Username is \(userName)")
    }

.onError()

The .onError() method is used to handle any failure that occurs in the promise chain. It will catch an error from the initial promise or from any subsequent .then() block. Once an error is caught, the success path (.then() blocks) is skipped, and execution jumps to the nearest .onError().

fetchFailingData()
    .then { data -> Promise<User> in
        // This block is skipped
        return parseUser(from: data)
    }
    .then { user in
        // This block is also skipped
        print("User: \(user.name)")
    }
    .onError { error in
        // The error is caught here
        print("An error occurred: \(error)")
    }

.finally()

The .finally() method is always executed, whether the promise chain succeeds or fails. It's the ideal place for cleanup code, such as hiding a loading indicator or closing a resource. It does not receive any value or error and does not alter the chain's outcome.

showLoadingIndicator()

fetchData()
    .then(processData)
    .onError(handleError)
    .finally {
        hideLoadingIndicator()
    }