How to check if one of URLSession tasks returned an error and if so stop code execution?

I need to make 2 API calls simultaneously. I have 2 URLs for the calls, and if one of the calls will return any error I want to stop all the code execution.

How I tried to do it:

  1. I have a function called performRequest() with a completion block. I call the function in my ViewController to update the UI - show an error/or a new data if all was successful. Inside it I create a URLSession tasks and then parse JSON:
  2. I created an array with 2 urls:
func performRequest(_ completion: @escaping (Int?) -> Void) {
 var urlArray = [URL]()

 guard let urlOne = URL(string: "https://api.exchangerate.host/latest?base=EUR&places=9&v=1") else { return }
 guard let urlTwo = URL(string: "https://api.exchangerate.host/2022-05-21?base=EUR&places=9") else { return }
 urlArray.append(urlOne)
 urlArray.append(urlTwo)
 }
  1. Then for each of the url inside the array I create a session and a task:
 urlArray.forEach { url in
     let session = URLSession(configuration: .ephemeral)
     let task = session.dataTask(with: url) { data, _, error in
         if error != nil {
             guard let error = error as NSError? else { return }
             completion(error.code)
             return
         }
         if let data = data {
             let printData = String(data: data, encoding: String.Encoding.utf8)
             print(printData!)
             DispatchQueue.main.async {
                 self.parseJSON(with: data)
             }
         }
     }
     task.resume()
 }
 print("all completed")
 completion(nil)
}

For now I receive print("all completed") printed once in any situation: if both tasks were ok, if one of them was ok or none of them.

What I want is to show the print statement only if all tasks were completed successfully and to stop executing the code if one of them returned with error (for example if we will just delete one of the symbols in url string which will take it impossible to receive a data ).

How can I do it correctly?

Something like this will do.
func load(urls: [URL], completion: @escaping (Result<[Data], Error>) -> Void) {
    var datas = [Data](repeating: Data(), count: urls.count)
    var completed = 0
    var hadError = false
    
    let session = URLSession(configuration: .ephemeral)
    
    let tasks = urls.enumerated().map { index, url in
        session.dataTask(with: url) { data, response, error in
            if let error = error ?? response?.error {
                if !hadError {
                    hadError = true
                    completion(.failure(error))
                    session.invalidateAndCancel()
                }
            } else {
                datas[index] = data!
                completed += 1
                if completed == urls.count {
                    completion(.success(datas))
                }
            }
        }
    }
    tasks.forEach { $0.resume() }
}

Note that URLResponse might have an error.
Also, normally JSON decoding can be done on a background queue.

2 Likes

Hi, tera. Thank you very much for a kind reply!

It was really helpful, so I succesfully adapted your code for my needs.

Also, normally JSON decoding can be done on a background queue.

I am a new to SWIFT, so in my case my code updates the UI straight after JSON parsing. I've read somewhere that if you' ll need parsed data somewhere in the future - then use a background queue, if not - better to wrap it in a main thread since you interact with a UI.

Am I understood it correct or was wrong?

You may decode JSON on a background queue and then update UI on the main queue. Pseudocode:

load(urls) { resut in
    dispatchPrecondition(.notOnQueue(.main))
    switch result {
        case .success(datas):
            let a = datas[0].decodeJson(A.self)
            let b = datas[1].decodeJson(B.self)
            DispatchQueue.main.async {
                // assuming a & b are published properties of your model object:
                model.a = a
                model.b = b
            }
    }
}
3 Likes

Got it. Thank you very much!