Preferred way to handle iterators that can fail?

I’ve had to use/write several iterators where the next() method has a signature similar to this:

pub fn next(*it: Iter) error{Doh}!?Item

I’m curious to see how the community prefers to handle these cases. I typically use one of three depending on the context:

// 1. try-ing the error
// I like this when I don't care about handling the iterator error, but
// I find that often when I'm using an iterator I don't want the caller
// to fail when the iterator fails - I'd rather treat it as the "natural"
// end of the iteration.
while (try it.next()) |item| {...}

// 2. Unwrapping the optional
// Usually my go-to. I typically use this when an error always means that the 
// iteration should end, but the caller can continue.
while (it.next()) |maybe_item| {
    if (maybe_item) |item| { ... } else break;
} else |err| { ... }

// 3. while true
// I use this the least because it's a bit awkward. This example is very
// simplified - usually in the catch block I will handle errors in different
// ways. Generally, at least one error will still allow the iteration to continue.
while (true) {
    const item = it.next() catch { break; } orelse break;
    ...
}

Do you have a preferred way of handling these iterations? Did I miss any obvious alternatives?

while (canError()) |res| if (res) |payload| {
    // happy path
} else break else |err| {
   // Error handler
}

#2, but with result and value. I like @mnemnion‘s compaction, though maybe less legible at first sight(?)