skip to Main Content

I have a requirement to send two independent requests to two remote APIs and need to process both responses at once when both requests are completed. I did the basic implementation using Zip operator. It works really fine in the happy scenario. Please check below sample code.

import Foundation
import Combine

enum NetowrkError: Error {
    case decodingError
    case requestFailed
}

struct StudentDTO: Codable {
    let name: String
    let age: Int
    let addressId: Int
}

struct AddressDTO: Codable {
    let id: Int
    let town: String
}

struct Student {
    let name: String
    let age: Int
    let town: String
}

func m1<T: Codable>(url: String, type: T.Type) -> Future<T, NetowrkError> {
    return Future { promise in
//Send request using URLSessionDatatask
    }
}

Publishers.Zip(
    m1(url: "",type: [StudentDTO].self),
    m1(url: "",type: [AddressDTO].self)
).sink(receiveCompletion: { print($0) },
       receiveValue: { studentList, addresses in
    //Process Both Resutls and join Student and Address to have a single Model
    let addressDict = addresses.reduce(into: [Int: String]()) {
        print($1)
        $0[$1.id] = $1.town
    }
    let students = studentList.map { student in
        return Student(name: student.name, age: student.age, town: addressDict[student.addressId] ?? "")
    }
    //self?.processStudents(students: students)
})

But when it comes to error handling with the Zip operator it seems a bit difficult. Because the Zip operator emits only when both requests get successful. My requirement is to show an error message when a request to Studen API get failed but should be able to proceed in the app even if the call to address the endpoint get failed. How can I do it with Combine?

2

Answers


  1. When a Swift Combine publisher fails it completes. You can use the .sink { completion in operator to handle your error. This will even work if only one publisher emits an error.

    e.g.:

    .sink { completion in
        switch completion{
        case .finished:
            break
        case .failure(let error):
            //handle error here
        }
    }
    

    Edit:

    A more complete implementation would look like this:

    Publishers.Zip(
        m1(url: "",type: [StudentDTO].self),
        m1(url: "",type: [AddressDTO].self)
    )
    .sink(receiveCompletion: { completion in
            switch completion{
            case .finished:
                break
            case .failure(let error):
                // handle error here
            }
        } , receiveValue: { studentList, addresses in
        //Process Both Resutls and join Student and Address to have a single Model
        let addressDict = addresses.reduce(into: [Int: String]()) {
            print($1)
            $0[$1.id] = $1.town
        }
        let students = studentList.map { student in
            return Student(name: student.name, age: student.age, town: addressDict[student.addressId] ?? "")
        }
        //self?.processStudents(students: students)
    })
    

    Edit 2:

    If you want to handle your errors separately you would need to do that inside your .Zip operator. .mapError(... seems appropriate for that.

    e.g.:

    Publishers.Zip(
        m1(url: "",type: [StudentDTO].self),
        m1(url: "",type: [AddressDTO].self)
            .mapError({ error in
                // handle error here and than return it so the publisher can finish
                return error
            })
    )
    
    Login or Signup to reply.
  2. In your case replaceError(with:) should help. With this function you can replace errors with a default value.

    It would look somethink like this:

    Publishers.Zip(
        m1(url: "",type: [StudentDTO].self),
        m1(url: "",type: [AddressDTO].self).replaceError(with: []).setFailureType(to: Error.self)
    )
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search