Aprende a usar los Publishers Just y Fail en Combine
Aprende a usar los Publishers Just y Fail en Combine

Publishers Combine: Just y Fail

En Combine podemos utilizar el Publisher Just y Fail. El Publisher Just se encarga de publicar un valor y una vez publicado finaliza. Y el Publisher Fail se encarga de publicar un error y automáticamente el publisher finaliza de enviar eventos. Estos dos publishers son muy útiles

SwiftBeta

Tabla de contenido


👇 SÍGUEME PARA APRENDER SWIFTUI, SWIFT, XCODE, etc 👇
Publishers Just y Fail en Combine
Publishers Just y Fail en Combine

Hoy en SwiftBeta vamos a introducir 2 nuevos Publishers. Estos Publishers son Just y Fail, y permiten crear un Publisher para publicar un valor o error. Vamos a ver un ejemplo práctico de por qué es útil conocer estos 2 nuevos Publishers, y para aprender a usarlos, vamos a crear un método llamado register que va a simular que hemos registrado un usuario en nuestro servidor, en nuestro backend. Este método va a retornar un Publisher que hemos creado en la implementación de nuestra función.

Creamos nuevas Page de Xcode

Lo primero de todo que vamos hacer es continuar con la serie de Combine, y como son dos Publishers nuevos los que vamos a aprender hoy, vamos a crear una nueva page llamada "Publishers Just & Fail".

Lo primero de todo importamos Combine y creamos la siguiente struct User con 2 propiedades:

struct User {
    let email: String
    let name: String
}
Creamos la Struct User en Swift

Esta struct User nos va a servir como modelo para el código que vamos a crear a continuación. Ahora ya podemos crear una instancia de nuestro nuevo tipo User:

let user: User = .init(email: "swiftbeta.blog@gmail.com", name: "SwiftBeta")
Instanciamos el tipo User

Una vez tenemos el modelo creado, te voy a introducir 2 nuevos tipos de Publisher. Hasta ahora hemos visto 2 Publishers llamados PassthroughtSubject y CurrentValueSubject (si no has visto el video te lo dejo por aquí arriba, ya que son muy útiles).

Y en este video vamos a aprender 2 nuevos, empezamos por el primero:

Just

Permite que se publiquen valores, y no permite que se puedan publicar errores. Si te acuerdas, al crear un Publisher debemos especificar el tipo de Output y el tipo de Failure. En este caso el Output son los valores que podemos publicar (cualquier tipo) y el Failure es el tipo de error, y en este caso es por defecto Never. Es decir, Just es un Publisher que emite su valor una vez y luego se completa.

Para verlo más claro, podemos crear un Publisher Just de la siguiente manera:

let justPublisher = Just(user)
Creamos un Publisher Just

Y podemos suscribirnos al Publisher utilizando el método sink que hemos visto a lo largo de toda la serie:

let cancellable = justPublisher.sink { completion in
    switch completion {
    case .failure(let error):
        print("Error \(error.localizedDescription)")
    case .finished:
        print("Finished")
    }
} receiveValue: { user in
    print("User: \(user)")
}
Sucripción al Publisher Just para recibir el valor de tipo User

Si compilamos obtenemos el siguiente resultado:

User: User(email: "swiftbeta.blog@gmail.com", name: "SwiftBeta")
Finished
Al compilar obtenemos este mensaje por la consola de Xcode

Con Just vemos que se ha publicado el valor, el suscriber lo ha recibido y el publisher ha finalizado. Una vez hemos visto este tipo de Publisher, ahora vamos a ver uno que es completamente lo opuesto, uno que solo nos permite publicar errores (Failures).

Fail

El Publisher Fail nos permite enviar errores en nuestro Publisher, es decir enviamos un error a través del publisher y automáticamente terminamos nuestro Publisher (impidiendo que se puedan seguir enviando valores a través de él). Para crear nuestro Publisher Fail, antes de todo debemos especificar qué tipo de errores queremos que viajen en nuestro Publisher, así que voy a crear un tipo llamado RegisterFormError y lo usaremos en el video de hoy:

enum RegisterFormError: String, Error {
    case userExists = "User already exists"
    case wrongEmail = "Wrong Email"
    case wrongPassword = "Wrong Password"
    case unknown = "Unknown Error"
}
Creamos un tipo Error para publicar dentro de nuestro Publisher Fail

Una vez tenemos creado el tipo de error que queremos que viaje por nuestro Publisher, ahora vamos a crear el failPublisher:

let failPublisher = Fail<Any, RegisterFormError>(error: RegisterFormError.unknown)
Creamos el Publisher Fail

Ahora podemos suscribirnos a nuestro failPublisher utilizando el método sink como hemos hecho con el ejemplo de Just:

failPublisher.sink { completion in
    switch completion {
    case .failure(let error):
        print("Error \(error.localizedDescription)")
    case .finished:
        print("Finished")
    }
} receiveValue: { value in
    print("Value \(value)")
}
Suscripción al Publisher Fail para recibir el error

Si compilamos vemos que obtenemos el error. En este caso, sigue otro flujo diferente al utilizar el Just, en lugar de ejecutarse el receiveValue, se ejecuta el completion pero con el case del failure.

Perfecto, ya tenemos estos 2 Publishers introducidos, ya podemos crear el  método que se encargará de registrar a un usuario en nuestro servidor.

Empezamos por la firma de la función:

func register(user: User) -> AnyPublisher<User, RegisterFormError>
Firma de la función que va a simular el registro de un User

A nuestro método register le va a llegar un parámetro de entrada que es el User que queremos registrar en nuestro backend, en nuestro Servidor. Y como resultado hay dos escenarios posibles:

  • Podemos obtener el User (indicando que todo ha ido correctamente)
  • Podemos obtener un Error de tipo RegisterFormError (indicando que ha habido un error al registrar a nuestro User)

Fíjate que estamos usando el tipo AnyPublisher, este tipo lo usamos para ocultar el tipo concreto de un Publisher, y nos facilita mucho el desarrollo usando Combine, vamos a verlo en detalle más adelante.

Ahora vamos a crear la implementación de nuestro método, si el email del user es swiftbeta.blog@gmail.com haremos que el registro funcione correctamente y sino devolveremos un error.

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 user
    } else {
        return RegisterFormError.wrongEmail
    }
}
Primer paso de la función register

Pero ¿qué ocurre si intentamos compilar? Obtenemos un error, ya que debemos retornar un Publisher, y dentro del Publisher debemos tener el valor que queremos publicar. En este caso, debemos publicar un valor para el caso del Just y un error para el caso del Fail. Pues vamos a empaquetar estos valores en sus Publishers correspondientes:

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)
    } else {
        return Fail(error: RegisterFormError.wrongEmail)
    }
}
Segundo paso de la función register

Si ahora intentamos compilar obtenemos otro error. Vamos a centrarnos en el caso del Just, nos dice que el tipo que espera la función no es de tipo Just:

Cannot convert return expression of type 'Just<User>' to return type 'AnyPublisher<User, RegisterFormError>'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)
    /*} else {
        return Fail(error: RegisterFormError.wrongEmail)
    }*/
}
Comentamos el código y nos centramos en el Publisher Just

Para arreglarlo, debemos cambiar el tipo del error, en lugar de que sea Never debemos especificar que sea de tipo RegisterFormError, lo transformamos para que sea igual que en la firma de la función y así el compilador no nos de error, y para hacerlo usamos el método setFailureType de la siguiente manera:

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)
    /*} else {
        return Fail(error: RegisterFormError.wrongEmail)
    }*/
}
Utilizamos el método setFailureType

Y si ahora intentamos compilar nos da otro error, lo vamos a solucionar usando el método .eraseToAnyPublisher(), este método es muy común cuando trabajamos con Combine:

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)
    }*/
}
Finalmente, utilizamos el método eraseToAnyPublisher

Con esto lo que hacemos es transformar nuestro Just Publisher a AnyPublisher y por lo tanto cumplimos el contrato con la firma de nuestra función. Ahora vamos a continuar con el else, descomentamos el código que hemos comentado hace un momento, y en este caso solo usamos el método eraseToAnyPublisher(). Si compilamos vemos que funciona perfectamente.

Una vez hemos llegado hasta aquí, podemos probar nuestro Publisher. Lo probamos primero con un valor correcto, con el correo específico que hemos puesto en el if, y vemos que funciona correctamente. Ahora vamos a modificar el email, al compilar vemos que nuestro Publisher publica un error. Todo funciona correctamente.

register(user: user)
    .sink { completion in
          switch completion {
          case .failure(let error):
              print("Error \(error.localizedDescription)")
          case .finished:
              print("Finished")
          }
      } receiveValue: { value in
          print("Value \(value)")
      }
Suscripción al método register para "escuchar" los valores o errores que recibimos

Conclusión

Hoy hemos aprendido a crear nuetras propiedas funciones y que retornen un Publisher según la lógica que implementamos dentro de nuestra función. para hacerlo hemos usado Just y Fail, dos nuevos Publishers que hemos introducido a la serie de Combine