Skip to content

v4 error handling #4711

@BoD

Description

@BoD

Error handling is an important aspect of a client library and as we’re looking at 4.0, we think it can be improved and are seeking feedback.

Exceptions vs GraphQL errors

2 kinds of errors need to be handled:

  • Non-GraphQL errors: typically network issues and cache misses
  • GraphQL errors: an operation has correctly been executed and a result has been received, and it contains a non empty errors field

Current situation

Currently, non-GraphQL errors are surfaced by throwing exceptions, whereas GraphQL errors are returned in ApolloResponse.errors. (See v3 doc error section).

Some drawbacks:

  • These 2 different ways make it difficult to handle errors at the same area of the code
  • Exceptions must be caught or they will crash your application
  • Exceptions are terminal in Flows:
    • surfacing first a cache miss and then a network result (e.g. when using CacheFirst strategy) is not straightforward

Proposed change

Similarly to ApolloResponse.errors, a new field ApolloResponse.exception: ApolloException is added, and the response is emitted (.toFlow()) or returned (.execute()), instead of throwing.

execute():

Before
try {
  val response = client.query(MyQuery()).execute()
  if (response.data != null) {
    // Handle (potentially partial) data
  } else {
    // Handle GraphQL errors
  }
} catch (e: ApolloException) {
  // Handle network error
}
After
val response = client.query(MyQuery()).execute()
if (response.data != null) {
  // Handle (potentially partial) data
} else {
  // Something wrong happened
  if (response.exception != null) {
    // Handle non-GraphQL errors
  } else {
    // Handle GraphQL errors
  }
}

.toFlow():

Before
client.subscription(MySubscription()).toFlow()
.catch { e ->
  // Handle network error
}
.collect { response ->
  if (response.data != null) {
    // Handle (potentially partial) data
  } else {
    // Handle GraphQL errors
  }
}
After
client.subscription(MySubscription()).toFlow().collect { response ->
  if (response.data != null) {
    // Handle (potentially partial) data
  } else {
    // Something wrong happened
    if (response.exception != null) {
      // Handle non-GraphQL errors
    } else {
      // Handle GraphQL errors
    }
  }
}

This addresses the drawbacks above, and arguably is a more consistent API - at the cost of a breaking change. To ease the transition, methods that throw like in 3.x will be added. Projects will be able to use them temporarily to keep the old behavior and migrate to the new one progressively.

Partial data and nullability

In the area of error handling, pain points related to handling GraphQL errors and nullability have also been identified:

  • Some projects don't need to handle partial data, or only at a few specific places
  • Many projects have a lot of nullable fields in their schemas that in reality can rarely be null (only in error cases)

To improve usability around this, v4 will introduce:

  • Error aware parsing: GraphQL errors are handled at parse time and data is guaranteed null when there are errors / non-null otherwise
  • @catch and FieldResult to handle partial data for specific fields
  • @semanticNonNull to make fields generated as non null

This is introduced in #5405, and documentation is in #5407.

Feedback

Please comment on this ticket with any feedback you may have!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions