Home > Back-end >  How to handle error when using multiple async request with Combine Framework
How to handle error when using multiple async request with Combine Framework

Time:01-03

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?

CodePudding user response:

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)
)

CodePudding user response:

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)
})
  • Related