I have a function that fetches data from a URL:
private func fetch(url: URL) -> String? {
var htmlString: String?
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard error == nil, let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
return
}
if let mimeType = httpResponse.mimeType, mimeType == "text/html",
let data = data,
let string = String(data: data, encoding: .utf8) {
htmlString = string
}
}
task.resume()
return htmlString
}
This function works in a playground, when I test it as part of a package in XCode, it doesn't seem to run past the let task...
statement. How do I fix this?
CodePudding user response:
The dataTask
rendition that returns the result immediately would only work if your device happened to have a cached response ready to deliver. Otherwise, it would return
before the dataTask
had a chance to complete.
One needs to wait for the response from the server before returning the result. In Swift concurrency, we would await
:
enum WebError: Error {
case notSuccess(Int)
case notHtml
}
private func fetch(url: URL) async throws -> String? {
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
guard 200...299 ~= response.statusCode else {
throw WebError.notSuccess(response.statusCode)
}
guard
response.mimeType == "text/html",
let string = String(data: data, encoding: .utf8)
else {
throw WebError.notHtml
}
return string
}
Note, I also throw errors, so that if something goes wrong, the caller can catch
the error and successfully diagnose what went wrong.
See WWDC 2021 video Use async/await with URLSession.