0
# Error Handling
1
2
Comprehensive error handling system for GraphQL and network errors, providing unified error representation and detailed error information for debugging and user feedback.
3
4
## Capabilities
5
6
### CombinedError Class
7
8
Unified error class that combines GraphQL errors and network errors into a single error instance.
9
10
```typescript { .api }
11
/**
12
* Combined error class for GraphQL and network errors
13
*/
14
class CombinedError extends Error {
15
/** Network-level error (fetch failures, timeouts, etc.) */
16
networkError?: Error;
17
/** Array of GraphQL execution errors */
18
graphQLErrors: GraphQLError[];
19
/** HTTP response that caused the error */
20
response?: Response;
21
22
/**
23
* Create a new CombinedError
24
* @param params - Error parameters
25
*/
26
constructor(params: {
27
networkError?: Error;
28
graphQLErrors?: readonly GraphQLError[];
29
response?: Response;
30
});
31
32
/** Error message combining network and GraphQL errors */
33
message: string;
34
/** Error name */
35
name: string;
36
}
37
```
38
39
**Usage Examples:**
40
41
```typescript
42
import { CombinedError } from "@urql/core";
43
44
// Handle errors from query results
45
const result = await client.query(GetUserQuery, { id: "123" }).toPromise();
46
47
if (result.error) {
48
const error = result.error;
49
50
// Check for network errors
51
if (error.networkError) {
52
console.error("Network error:", error.networkError.message);
53
54
// Handle specific network errors
55
if (error.networkError.message.includes('Failed to fetch')) {
56
showOfflineMessage();
57
}
58
}
59
60
// Check for GraphQL errors
61
if (error.graphQLErrors.length > 0) {
62
error.graphQLErrors.forEach(gqlError => {
63
console.error("GraphQL error:", gqlError.message);
64
65
// Handle specific GraphQL error types
66
if (gqlError.extensions?.code === 'UNAUTHENTICATED') {
67
redirectToLogin();
68
}
69
});
70
}
71
72
// Access HTTP response for status codes
73
if (error.response) {
74
console.log("Response status:", error.response.status);
75
console.log("Response headers:", error.response.headers);
76
}
77
}
78
79
// Create custom combined errors
80
const customError = new CombinedError({
81
networkError: new Error("Connection timeout"),
82
graphQLErrors: [{
83
message: "User not found",
84
locations: [{ line: 2, column: 3 }],
85
path: ["user"],
86
extensions: { code: "USER_NOT_FOUND" }
87
}],
88
});
89
```
90
91
### Error Result Creation
92
93
Create error operation results for custom exchanges and error handling.
94
95
```typescript { .api }
96
/**
97
* Create an error OperationResult from an error
98
* @param operation - The operation that failed
99
* @param error - Error instance (Error or CombinedError)
100
* @param response - Optional ExecutionResult with partial data
101
* @returns OperationResult with error information
102
*/
103
function makeErrorResult<Data = any, Variables extends AnyVariables = AnyVariables>(
104
operation: Operation<Data, Variables>,
105
error: Error | CombinedError,
106
response?: ExecutionResult
107
): OperationResult<Data, Variables>;
108
```
109
110
**Usage Examples:**
111
112
```typescript
113
import { makeErrorResult, CombinedError } from "@urql/core";
114
115
// In a custom exchange
116
const customExchange: Exchange = ({ forward }) => ops$ => {
117
return pipe(
118
ops$,
119
mergeMap(operation => {
120
try {
121
return forward(fromValue(operation));
122
} catch (error) {
123
// Create error result for exceptions
124
return fromValue(makeErrorResult(
125
operation,
126
new CombinedError({
127
networkError: error as Error
128
})
129
));
130
}
131
})
132
);
133
};
134
135
// Handle timeout errors
136
const timeoutExchange: Exchange = ({ forward }) => ops$ => {
137
return pipe(
138
ops$,
139
mergeMap(operation => {
140
const timeout$ = pipe(
141
timer(30000), // 30 second timeout
142
map(() => makeErrorResult(
143
operation,
144
new CombinedError({
145
networkError: new Error("Request timeout")
146
})
147
))
148
);
149
150
return pipe(
151
race([forward(fromValue(operation)), timeout$]),
152
take(1)
153
);
154
})
155
);
156
};
157
```
158
159
### GraphQL Error Types
160
161
Standard GraphQL error interface and extensions.
162
163
```typescript { .api }
164
interface GraphQLError {
165
/** Error message */
166
message: string;
167
/** Source locations where error occurred */
168
locations?: readonly GraphQLErrorLocation[];
169
/** Path to the field that caused the error */
170
path?: readonly (string | number)[];
171
/** Additional error information */
172
extensions?: GraphQLErrorExtensions;
173
}
174
175
interface GraphQLErrorLocation {
176
/** Line number in GraphQL document */
177
line: number;
178
/** Column number in GraphQL document */
179
column: number;
180
}
181
182
interface GraphQLErrorExtensions {
183
/** Error code for programmatic handling */
184
code?: string;
185
/** Additional error metadata */
186
[key: string]: any;
187
}
188
189
type ErrorLike = Partial<GraphQLError> | Error;
190
```
191
192
**Usage Examples:**
193
194
```typescript
195
// Handle different error types
196
result.error?.graphQLErrors.forEach(error => {
197
switch (error.extensions?.code) {
198
case 'UNAUTHENTICATED':
199
// Redirect to login
200
window.location.href = '/login';
201
break;
202
203
case 'FORBIDDEN':
204
// Show access denied message
205
showError("You don't have permission to perform this action");
206
break;
207
208
case 'VALIDATION_ERROR':
209
// Show field validation errors
210
if (error.extensions?.field) {
211
showFieldError(error.extensions.field, error.message);
212
}
213
break;
214
215
default:
216
// Generic error handling
217
showError(error.message);
218
}
219
});
220
```
221
222
### Error Context and Metadata
223
224
Access error context and debugging information.
225
226
```typescript { .api }
227
interface OperationResult<Data = any, Variables extends AnyVariables = AnyVariables> {
228
/** The operation that produced this result */
229
operation: Operation<Data, Variables>;
230
/** Result data (may be partial if errors occurred) */
231
data?: Data;
232
/** Combined error information */
233
error?: CombinedError;
234
/** Additional response metadata */
235
extensions?: Record<string, any>;
236
/** Whether result is stale and will be updated */
237
stale: boolean;
238
/** Whether more results will follow */
239
hasNext: boolean;
240
}
241
```
242
243
**Usage Examples:**
244
245
```typescript
246
// Comprehensive error logging
247
const result = await client.query(GetUserQuery, { id: "123" }).toPromise();
248
249
if (result.error) {
250
// Log full error context
251
console.group(`Error in ${result.operation.kind} operation`);
252
console.log("Operation:", result.operation.query);
253
console.log("Variables:", result.operation.variables);
254
console.log("Error:", result.error.message);
255
256
if (result.error.networkError) {
257
console.log("Network Error:", result.error.networkError);
258
}
259
260
if (result.error.graphQLErrors.length > 0) {
261
console.log("GraphQL Errors:", result.error.graphQLErrors);
262
}
263
264
if (result.error.response) {
265
console.log("Response Status:", result.error.response.status);
266
console.log("Response Headers:", [...result.error.response.headers.entries()]);
267
}
268
269
console.groupEnd();
270
}
271
272
// Handle partial data with errors
273
if (result.data && result.error) {
274
// Some fields succeeded, others failed
275
console.log("Partial data received:", result.data);
276
console.log("Errors for specific fields:", result.error.graphQLErrors);
277
278
// Use partial data but show error indicators
279
displayPartialData(result.data, result.error.graphQLErrors);
280
}
281
```
282
283
### Error Handling Patterns
284
285
Common patterns for handling errors in different scenarios.
286
287
```typescript { .api }
288
// Retry pattern with exponential backoff
289
async function executeWithRetry<T>(
290
operation: () => Promise<OperationResult<T>>,
291
maxRetries = 3
292
): Promise<OperationResult<T>> {
293
let lastError: CombinedError | undefined;
294
295
for (let attempt = 0; attempt <= maxRetries; attempt++) {
296
try {
297
const result = await operation();
298
299
if (!result.error) {
300
return result;
301
}
302
303
// Don't retry GraphQL errors (they won't change)
304
if (result.error.graphQLErrors.length > 0 && !result.error.networkError) {
305
return result;
306
}
307
308
lastError = result.error;
309
310
if (attempt < maxRetries) {
311
// Exponential backoff
312
await new Promise(resolve =>
313
setTimeout(resolve, Math.pow(2, attempt) * 1000)
314
);
315
}
316
} catch (error) {
317
lastError = error as CombinedError;
318
}
319
}
320
321
throw lastError;
322
}
323
324
// Usage
325
try {
326
const result = await executeWithRetry(() =>
327
client.query(GetUserQuery, { id: "123" }).toPromise()
328
);
329
// Handle successful result
330
} catch (error) {
331
// Handle final error after retries
332
}
333
```
334
335
## Types
336
337
### Error Types
338
339
```typescript { .api }
340
class CombinedError extends Error {
341
networkError?: Error;
342
graphQLErrors: GraphQLError[];
343
response?: Response;
344
345
constructor(params: {
346
networkError?: Error;
347
graphQLErrors?: readonly GraphQLError[];
348
response?: Response;
349
});
350
}
351
352
interface GraphQLError {
353
message: string;
354
locations?: readonly GraphQLErrorLocation[];
355
path?: readonly (string | number)[];
356
extensions?: GraphQLErrorExtensions;
357
}
358
359
interface GraphQLErrorLocation {
360
line: number;
361
column: number;
362
}
363
364
interface GraphQLErrorExtensions {
365
code?: string;
366
[key: string]: any;
367
}
368
369
type ErrorLike = Partial<GraphQLError> | Error;
370
```
371
372
### Response Types
373
374
```typescript { .api }
375
interface ExecutionResult {
376
data?: null | Record<string, any>;
377
errors?: ErrorLike[] | readonly ErrorLike[];
378
extensions?: Record<string, any>;
379
hasNext?: boolean;
380
incremental?: IncrementalPayload[];
381
pending?: readonly PendingIncrementalResult[];
382
}
383
```