We use cookies to enhance your experience on the site
CodeWorlds

Advanced Error Handling

In space, things often don't go according to plan - communication can be interrupted, servers can be overloaded, data can be corrupted. Good error handling is the difference between a successful mission and a disaster!

Try/Catch/Finally - The Full Pattern

1async function fetchWithFullErrorHandling(url) {
2  try {
3    // Attempt to fetch data
4    const response = await fetch(url);
5
6    if (!response.ok) {
7      // Different messages for different error codes
8      switch (response.status) {
9        case 400:
10          throw new Error('Invalid request');
11        case 401:
12          throw new Error('Not authorized - please log in');
13        case 403:
14          throw new Error('Access denied to this resource');
15        case 404:
16          throw new Error('Resource not found');
17        case 429:
18          throw new Error('Too many requests - try again later');
19        case 500:
20          throw new Error('Server error - try again later');
21        default:
22          throw new Error(\`Unknown error: \${response.status}\`);
23      }
24    }
25
26    return await response.json();
27  } catch (err) {
28    // Handle network errors (no internet, timeout)
29    if (err instanceof TypeError) {
30      throw new Error(
31        'Network error - check your internet connection'
32      );
33    }
34    throw err; // Re-throw other errors
35  }
36}

ErrorBoundary Component

React offers Error Boundaries - components that catch rendering errors in their children:

1import React from 'react';
2
3class ErrorBoundary extends React.Component {
4  constructor(props) {
5    super(props);
6    this.state = { hasError: false, error: null };
7  }
8
9  static getDerivedStateFromError(error) {
10    return { hasError: true, error };
11  }
12
13  componentDidCatch(error, errorInfo) {
14    console.error('ErrorBoundary caught an error:', error);
15    console.error('Stack:', errorInfo.componentStack);
16  }
17
18  render() {
19    if (this.state.hasError) {
20      return (
21        <div className="error-boundary">
22          <h2>Something went wrong</h2>
23          <p>{this.state.error?.message}</p>
24          <button onClick={() => this.setState({
25            hasError: false, error: null
26          })}>
27            Try again
28          </button>
29        </div>
30      );
31    }
32
33    return this.props.children;
34  }
35}
36
37// Usage:
38function App() {
39  return (
40    <ErrorBoundary>
41      <SpaceDashboard />
42    </ErrorBoundary>
43  );
44}

Toast Notifications

Instead of displaying errors in place of the component, you can use toast notifications:

1import React, { useState, createContext, useContext } from 'react';
2
3const ToastContext = createContext();
4
5function ToastProvider({ children }) {
6  const [toasts, setToasts] = useState([]);
7
8  const addToast = (message, type = 'error') => {
9    const id = Date.now();
10    setToasts(prev => [...prev, { id, message, type }]);
11    // Auto-remove after 5 seconds
12    setTimeout(() => {
13      setToasts(prev => prev.filter(t => t.id !== id));
14    }, 5000);
15  };
16
17  return (
18    <ToastContext.Provider value={{ addToast }}>
19      {children}
20      <div className="toast-container">
21        {toasts.map(toast => (
22          <div key={toast.id} className={\`toast toast-\${toast.type}\`}>
23            {toast.message}
24          </div>
25        ))}
26      </div>
27    </ToastContext.Provider>
28  );
29}
30
31function useToast() {
32  return useContext(ToastContext);
33}
34
35// Usage in a component:
36function DataComponent() {
37  const { addToast } = useToast();
38
39  const fetchData = async () => {
40    try {
41      const response = await fetch('/api/data');
42      if (!response.ok) throw new Error('Server error');
43      const data = await response.json();
44      addToast('Data fetched successfully!', 'success');
45    } catch (err) {
46      addToast(err.message, 'error');
47    }
48  };
49
50  return <button onClick={fetchData}>Fetch data</button>;
51}

Retry with Exponential Backoff

When the server is overloaded, it's worth waiting progressively longer between retries:

1async function fetchWithRetry(url, maxRetries = 3) {
2  for (let attempt = 0; attempt <= maxRetries; attempt++) {
3    try {
4      const response = await fetch(url);
5
6      if (response.status === 429 || response.status >= 500) {
7        // Server overloaded or server error
8        if (attempt < maxRetries) {
9          // Exponential backoff: 1s, 2s, 4s
10          const delay = Math.pow(2, attempt) * 1000;
11          console.log(
12            \`Attempt \${attempt + 1} failed. \
13Retrying in \${delay}ms...\`
14          );
15          await new Promise(resolve =>
16            setTimeout(resolve, delay)
17          );
18          continue;
19        }
20      }
21
22      if (!response.ok) {
23        throw new Error(\`HTTP Error: \${response.status}\`);
24      }
25
26      return await response.json();
27    } catch (err) {
28      if (attempt === maxRetries) {
29        throw new Error(
30          \`Failed after \${maxRetries + 1} attempts: \${err.message}\`
31        );
32      }
33
34      const delay = Math.pow(2, attempt) * 1000;
35      await new Promise(resolve => setTimeout(resolve, delay));
36    }
37  }
38}

Clear error feedback and recovery mechanisms are the key to a great user experience. Never leave the user without information about what went wrong and what they can do!

Go to CodeWorlds