Until recently, custom iterators were probably the most common way to iterate over a list of elements that might trigger an error. Several examples exist in the standard library. Perhaps the most well known would be the sql.Rows type, which provides (among others), the following methods:
Next() bool— Advances to the next itemScan(...any) error— Processes the current itemErr() error— Reports an iteration errorClose() error— Closes iterator, possibly before the last item has been processed
These four methods are pretty standard, in any custom iterator implementation, though Scan() will typically be replaced with an implementation-specific method to process the current result, and in some cases Err() and Close() may be combined.
Let’s see what this might look like for our orders example:
orders, err := db.Orders(ctx, userID)
if err != nil {
return err
}
defer orders.Close() // Ensure that the iterator is cleaned up, even in case of early return
for orders.Next() {
order, err := orders.Process()
if err != nil {
return err
}
/* Do something with each order */
}
if err := orders.Err(); err != nil {
return err
}
This approach is infinitely flexible. It allows exposing specific errors at various places (in the example, four places: db.Orders, orders.Process(), orders.Err(), and orders.Close()). And it allows the addition of an arbitrary number of other methods, which might be used to expose per-item or per-query metadata, or to do different types of processing.