Aprende a usar el operador flatMap en Combine
Aprende a usar el operador flatMap en Combine

Manejo de flatMap y catch: Operadores Esenciales en Combine

Descubre cómo usar flatMap en Combine para concatenar Publishers. Aprende a gestionar eventos de manera eficiente en tus apps de Swift.

SwiftBeta

Tabla de contenido


👇 SÍGUEME PARA APRENDER SWIFTUI, SWIFT, XCODE, etc 👇
Operador FlatMap y Catch en Combine. Curso de Combine en Swift
Operador FlatMap y Catch en Combine. Curso de Combine en Swift

Hoy en SwiftBeta vamos a explicar el operador flatMap en Combine. Este operador tiene un potencial tremendo y es muy versátil, en el video de hoy vamos a aprender a cómo concatenar el resultado de varios Publishers. Imagina que quieres registrar un usuario en tu servidor, y justo a continuación, después de hacer la petición HTTP,  si el user se ha registrado correctamente guardamos el usuario en la base de datos del iPhone, pues bien, podemos concatenar estas dos llamadas, estos dos métodos y suscribirnos al Publisher, como resultado el código que nos queda es muy fácil de interpretar y leer.

Vas a ver que el uso de flatMap es muy sencillo pero potente, vamos a crear 2 métodos. Uno que nos permita simular que hemos realizado el registro en nuestro servidor correctamente, y el otro método va a ser para simular que almacenamos el user que hemos registrado en una base de datos del iPhone.

Creamos nuevas Page de Xcode

Lo primero de todo que vamos hacer es continuar con la serie de Combine, y como es un nuevo operador, vamos a crear una nueva page llamada "Operador FlatMap".

Lo siguiente que vamos hacer es crear 2 nuevos tipos, primero un tipo User que será el tipo Output de nuestro Publisher:

struct User {
    let email: String
    let name: String
}
Creamos el tipo User en Swift

Ya que estamos creamos una instancia de User:

let user: User = .init(email: "swiftbeta.blog@gmail.com", name: "SwiftBeta")
Creamos una instancia de User

Y ahora creamos el otro tipo, va a ser un Enum que será el tipo Failure de nuestro Publisher:

enum RegisterFormError: String, Error {
    case userExists = "User already exists"
    case wrongEmail = "Wrong Email"
    case wrongPassword = "Wrong Password"
    case unknown = "Unknown Error"
}
Creamos un tipo de Error nuevo para usar el post de hoy

A continuación creamos el método que va a simular que registramos un user. Es decir, si el correo electrónico del user es swiftbeta.blog@gmail.com todo irá bien pero si el correo electrónico es otro publicamos un error. Vamos a usar el mismo método que vimos en el anterior video, así que puedes ir a su Page del Playground y copiarlo directamente.

Fíjate que para crear la implementación usamos Just y Fail que lo vimos en el anterior video.

func register(user: User) -> AnyPublisher<User, RegisterFormError> {
    // Petición HTTP al backend para guardar el user en Base de Datos
    if user.email == "swiftbeta.blog@gmail.com" {
        return Just(user)
        .setFailureType(to: RegisterFormError.self)
        .eraseToAnyPublisher()
    } else {
        return Fail(error: RegisterFormError.wrongEmail)
    }
}
Lógica del método que se va a encargar de simular que registramos un user

Lo siguiente que vamos hacer es crear el método para almacenar en la base de datos. La única validación que vamos a tener, es que si el user tiene de nombre SwiftBeta, lo podremos almacenar en la base de datos, y sino retornaremos un error:

func save(user: User) -> AnyPublisher<Bool, RegisterFormError> {
    // Guardamos al user en la base de datos
    if user.name == "SwiftBeta" {
        return Just(true)
            .setFailureType(to: RegisterFormError.self)
            .eraseToAnyPublisher()
    } else {
        return Fail(error: RegisterFormError.userExists)
            .eraseToAnyPublisher()
    }
}
Lógica del método que se va a encargar de simular que almacenamos en la base de datos un user

En este caso, si el user se llama SwiftBeta simulamos que hemos almacenado correctamente al User en la base de datos, y sino Publicamos un error de que el usuario ya existe.

Ya tenemos los dos métodos, ahora ya podemos concatenar nuestros 2 métodos para que si funciona correctamente el primero se llame al segundo. Escribimos el siguiente código:

register(user: user).flatMap { user in
    return save(user: user)
}
Usamos flatMap para concatenar 2 métodos (2 Publishers)

Añadimos el método register y a continuación utilizamos el operador flatMap. El operador flatMap tiene como parámetro de entrada la salida del método register, es decir un tipo User. Este valor que recibimos, lo inyectamos al segundo método llamado save, que simula que lo almacena en la base de datos. Y una vez tenemos esta parte, ya nos podemos suscribir al publisher utilizando el método sink:

register(user: user).flatMap { user in
    return save(user: user)
}
.sink(receiveCompletion: { hasFinished in
    switch hasFinished {
    case .finished:
        print("Registro completado")
    case .failure(let error):
        print(error.localizedDescription)
    }
}, receiveValue: { value in
    print("Usuario Registrado y almacenado en la base de datos registered -> \(value)")
})
Suscripción para recibir todos los valores e incluso los errores del Publisher

En este caso hemos usado flatMap para concatenar 2 métodos pero podríamos concatenar más lógica. Fíjate que si todo va bien recibimos el valor dentro del closure receiveValue, y si obtenemos un error, tanto del método register como del método save, se ejecuta el closure receiveCompletion.

Antes de acabar, como curiosidad y para enseñarte otro operador. Si queremos que nuestro suscriber no reciba errores, podemos utilizar el operador catch. Este operador es muy útil para capturar cualquier error que viaje en nuestro Publisher y así retornar otro Publisher con el valor, con el output que queramos, es decir:

register(user: user).flatMap { user in
    return save(user: user)
}
.catch({ error in
    Just(false)
})
.sink(receiveCompletion: { hasFinished in
    switch hasFinished {
    case .finished:
        print("Registro completado")
    case .failure(let error):
        print(error.localizedDescription)
    }
}, receiveValue: { value in
    print("Usuario Registrado y almacenado en la base de datos registered -> \(value)")
})
Utilizamos el operador Catch de Combine para interceptar el error que viaja en nuestro Publisher

En este caso si recibimos un error, lo que hacemos es retornar un false, indicando que algo no ha ido bien.

Conclusión

Hoy hemos aprendido a cómo usar flatMap desde 0 usando Combine. Antes de usarlo, hemos aprendido 2 tipos de Publishers que no habíamos visto hasta ahora, uno de ellos es Just y el otro Fail. El primero publica valores dentro del Publisher y el segundo Publica errores dentro del Publisher. Una vez hemos entendido estos conceptos hemos usado AnyPublisher como valor de retorno de 2 métodos y hemos usado flatMap para concatenar el Output de un método con el Input del otro.

Y hasta aquí el video de hoy!