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:
- A new value: This value will be passed to the next
.then()
in the chain, wrapped in a newPromise
. - 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()
}