swifterror-handlingoption-typeswiftsoup

Trying to do deal with errors and optionals the right way


I am attempting to use SwiftSoup to scrape some HTML. This example, based on the SwiftSoup github documentation, works fine…

func scrape() throws {

  do {
    let htmlFromSomeSource = "<html><body><p class="nerp">HerpDerp</p><p class="narf">HoopDoop</p>"

    let doc = try! SwiftSoup.parse(htmlFromSomeSource)
    let tag = try! doc.select("p").first()!
    let tagClass = try! tag.attr("class")
  } catch {
    print("oh dang")
    throw Abort(.notFound)
  }
  print(tagClass)
}

… Up until I mess with the selector or attribute targets, at which point everything crashes thanks to the implicitly unwrapped optionals (which I assume was just quick-and-dirty code to get smarter people started). That do/catch doesn't seem to help at all.

So what's the Right way? This compiles...

print("is there a doc?")
guard let doc = try? SwiftSoup.parse(response.body.description) else {
  print("no doc")
  throw Abort(.notFound)
}

print("should halt because there's no img")
guard let tag = try? doc.select("img").first()! else {
  print("no paragraph tag")
  throw Abort(.notFound)
}

print("should halt because there's no src")
guard let tagClass = try? tag.attr("src") else {
  print("no src")
  throw Abort(.notFound)
}

... but again if I mess with the selector or attribute it crashes out, "Unexpectedly found nil while unwrapping an Optional value" (after "is there a doc?"). I thought guard would halt the process when it encountered a nil? (If I convert "try?" to "try" the compiler complains that "initializer for conditional binding must have Optional type"…)


Solution

  • If you declare the function as throws you don't need a do - catch block inside the function. Just remove the block and the exclamation marks after try to pass through the errors to the caller function.

    func scrape() throws { // add a return type
        let htmlFromSomeSource = "<html><body><p class="nerp">HerpDerp</p><p class="narf">HoopDoop</p>"
    
        let doc = try SwiftSoup.parse(htmlFromSomeSource)
        guard let tag = try doc.select("p").first() else { throw Abort(.notFound) }
        let tagClass = try tag.attr("class")
        // return something 
    }