Skip to content
This repository has been archived by the owner on Jun 19, 2023. It is now read-only.

feat: use error channels #62

Closed
wants to merge 2 commits into from
Closed

feat: use error channels #62

wants to merge 2 commits into from

Commits on May 6, 2020

  1. feat: use error channels

    Refactor the API to use error channels where appropriate.
    
    Previously, asynchronous functions in this interface would return channels of
    "results" that can be either errors or values as shown below
    
        type Result struct {
          Value ValueType
          Err error
        }
        func Request(context.Context) (results <-chan Result)
    
    Unfortunately, there are several issues with this approach:
    
    The basic problem with this approach is that it requires defining a new "result"
    type and checking if each value is either a result or error. This is hardly a
    show-stopper but it infects values with additional error fields.
    
    However, the main issue with this approach is cancellation. If the context is
    canceled, the Request implementation has a few options:
    
    1. Close the results channel and walk away. Unfortunately, the caller will likely
       interpret this as "request competed" instead of "request canceled". To correctly
       use functions like this, the caller must check if the context after the channel
       closes.
    2. Leave the result channel open. In this case, the caller must select on its
       own context. This is a pretty sane approach, but it makes it impossible to
       safely use the results channel in a "for range" loop.
    3. Write the cancellation error to the results channel. This will ensure that
       the caller gets the error, but only if the caller is still reading from the
       channel. If the caller isn't reading from the channel, the request's
       goroutine will block trying to write to the channel and will never exit.
    
    This patch solves this problem by returning an error channel with a buffer of 1
    instead of a channel of results. This channel will yield at most one error
    before it's closed. This pattern is used across a wide range of go projects
    including Docker.
    
        func Request(context.Context) (values <-chan ValueType, err <-chan error)
    
    Unlike the previous API, this API is difficult to misuse.
    
    1. Errors are clearly returned on the err channel so the user won't assume that
       a closed value channel means the request succeeded.
    2. The value channel is closed so "for range" loops work.
    3. The error is always written to a channel with a buffer size of 1. If the
       caller doesn't listen on this channel (e.g., because it doesn't care about the
       error), nothing bad will happen.
    
    The correct usage is:
    
        values, err := Request(ctx)
        for v := range values {
            // do something
        }
        // wait for the request to yield the final error
        // (or close the channel with no error).
        return <-err
    
    The main downside to this approach is that it requires multiple channels.
    Stebalien committed May 6, 2020
    Configuration menu
    Copy the full SHA
    918c53b View commit details
    Browse the repository at this point in the history
  2. feat: improve docs

    Stebalien committed May 6, 2020
    Configuration menu
    Copy the full SHA
    3af1a2a View commit details
    Browse the repository at this point in the history