Skip to content

Capítulo 4.4: Tratamento de Erros

Tempo de estudo: 45 minutos


1. Tratamento de Erros: Protocolos de Emergência do Centro de Controle de Missão (CCM)

No espaço, nem tudo pode sair como planeado: uma explosão solar pode interromper a comunicação, o computador de bordo da nave pode falhar, e a equipa da Terra pode enviar coordenadas incorretas.

O tratamento de erros no frontend são os protocolos de emergência do seu CCM. Eles devem:

  • 🚨 Evitar que toda a interface "expluda" devido a um único comando falho.
  • 📡 Comunicar claramente ao operador (utilizador) o que exatamente deu errado.
  • 🔧 Sugerir possíveis ações futuras.

💡 Analogia espacial:

Se a nave envia um sinal 500 Internal Server Error, o ecrã do CCM não deve mostrar "Erro Crítico de JavaScript na linha 57". Em vez disso, deve aparecer: "🚨 Falha a bordo da nave! Os engenheiros já foram notificados. Tente repetir o comando mais tarde."


2. Tipos de "Anomalias Espaciais"

No frontend, deparamo-nos com três tipos principais de erros ao trabalhar com a API:

  1. Erros de Rede: A comunicação com o servidor não foi estabelecida. A antena não funciona, o cabo está cortado. fetch "cairá" no bloco .catch().
  2. Erros de Cliente (4xx): O comando da Terra estava incorreto. ID inválido, erro de validação. O servidor responde, mas com o status 4xx.
  3. Erros de Servidor (5xx): Falha na própria nave. Problema no código da API. O servidor responde, mas com o status 500+.

Já começámos a tratá-los com try...catch e a verificação de response.ok. Agora, vamos fazer isso de forma centralizada.


3. Função Centralizada de Manipulação

Repetir o mesmo código try...catch em cada função é uma má prática. Vamos criar um "wrapper" universal para as nossas requisições fetch.

Passo 1: Criar api.js Crie um novo ficheiro api.js ao lado de app.js. Nele, moveremos toda a lógica de interação com a API.

// api.js

const API_BASE_URL = 'http://127.0.0.1:8000';

/**
 * Função universal para executar requisições à API.
 * Lida com erros e retorna JSON.
 * @param {string} endpoint - Endpoint da API, por exemplo, '/spaceships'
 * @param {object} options - Parâmetros para fetch (method, headers, body)
 */
async function apiRequest(endpoint, options = {}) {
    const url = `${API_BASE_URL}${endpoint}`;

    try {
        const response = await fetch(url, options);

        // Se a resposta não for JSON, lança um erro imediatamente
        const contentType = response.headers.get('content-type');
        if (!contentType || !contentType.includes('application/json')) {
            // Exceção para requisições DELETE bem-sucedidas que não têm corpo
            if (response.status === 204) return null;

            throw new TypeError(`Resposta não-JSON recebida do servidor: ${response.statusText}`);
        }

        const data = await response.json();

        if (!response.ok) {
            // Se o servidor retornou JSON com um erro (por exemplo, detail do FastAPI)
            const errorMessage = data.detail || `Erro HTTP! Status: ${response.status}`;
            throw new Error(errorMessage);
        }

        return data;

    } catch (error) {
        console.error(`Erro na requisição API para ${endpoint}:`, error);
        // Propaga o erro para que possa ser capturado na UI
        throw error;
    }
}

Passo 2: Conectar api.js no index.html É importante conectá-lo ANTES de app.js, pois app.js usará as suas funções.

<!-- index.html -->
<body>
    <!-- ... -->
    <script src="api.js"></script>
    <script src="app.js"></script>
</body>

Passo 3: Refatorar app.js Agora, vamos reescrever as nossas funções usando o novo apiRequest.

// app.js

// const API_BASE_URL = ...; // Esta linha pode ser removida, agora está em api.js

// ...

async function fetchAndDisplayFleet() {
    try {
        fleetList.innerHTML = '<li>Carregando telemetria...</li>';
        const ships = await apiRequest('/spaceships'); // <-- Usamos nosso wrapper!

        fleetList.innerHTML = '';
        if (ships.length === 0) {
            fleetList.innerHTML = '<li>Não há nenhuma nave no registro.</li>';
            return;
        }

        ships.forEach(ship => { /* ... restante do código de exibição ... */ });
    } catch (error) {
        fleetList.innerHTML = `<li>🔴 Erro ao carregar a frota: ${error.message}</li>`;
    }
}

async function createShip(event) {
    event.preventDefault();
    const shipData = { /* ... coleta de dados do formulário ... */ };

    try {
        createStatusMessage.textContent = 'Enviando comando para lançamento...';
        const options = {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(shipData)
        };
        const newShip = await apiRequest('/spaceships', options); // <-- Usamos nosso wrapper!

        createStatusMessage.textContent = `🚀 Lançamento bem-sucedido! ID atribuído à nave: ${newShip.id}`;
        createShipForm.reset();
        fetchAndDisplayFleet();
    } catch (error) {
        createStatusMessage.textContent = `🔴 Erro: ${error.message}`;
    }
}

// Reescreva as outras funções (fetchShipById, deleteShip) de forma análoga!
Agora, toda a lógica de tratamento de erros de rede, verificação de response.ok e parsing de JSON está num único lugar, e o código em app.js tornou-se muito mais limpo e legível.


4. Exibição de Erros ao Usuário

Uma boa interface não deve apenas registar o erro na consola, mas também mostrá-lo ao utilizador de forma compreensível.

Exemplo: Melhoria de createShip O nosso código já faz isso: createStatusMessage.textContent = .... Mas podemos fazer ainda melhor, criando uma função universal para exibir notificações.

Adicionando em app.js:

// app.js
function showNotification(message, isError = false) {
    const notificationArea = document.getElementById('create-status-message'); // ou outro elemento
    notificationArea.textContent = message;
    notificationArea.style.color = isError ? 'red' : 'green';
}

// Usando em createShip:
async function createShip(event) {
    // ...
    try {
        // ...
        const newShip = await apiRequest('/spaceships', options);
        showNotification(`🚀 Lançamento bem-sucedido! ID: ${newShip.id}`);
        // ...
    } catch (error) {
        showNotification(`🔴 Erro: ${error.message}`, true);
    }
}
Agora temos um mecanismo unificado para exibir tanto mensagens de sucesso quanto erros.


Quiz para fixação

1. O bloco `.catch()` em uma promessa `fetch` será acionado se...

2. Por que é necessária uma função de tratamento centralizada para requisições API?

3. `response.headers.get('content-type')` é usado para...

4. `throw new Error(...)` dentro de `try...catch` ou `.then()` é usado para...

5. Por que é importante exibir os erros para o usuário, e não apenas no console?


🚀 Resumo do Capítulo:

Você fortaleceu seu Centro de Controle de Missões, criando protocolos de emergência robustos.

  • 🛡️ Você entende a diferença entre erros de rede, de cliente e de servidor.
  • ⚙️ Você criou uma função apiRequest centralizada para lidar com todas as requisições, evitando duplicação de código.
  • 📡 Sua interface agora é capaz de informar corretamente o usuário sobre os erros, tornando-a mais amigável e confiável.

Escudos de emergência levantados! Mas o que é melhor: cadeias .then() ou o moderno async/await? No próximo capítulo, analisaremos ambas as abordagens e entenderemos quando usar cada uma.

📌 Verificação:

  • Verifique se o seu código em app.js foi refatorado com sucesso e usa a nova função apiRequest.
  • Tente parar o servidor FastAPI e clique no botão "Solicitar Dados". Você deverá ver um erro de conexão na página.
  • Tente criar uma nave com dados inválidos. Você deverá ver uma mensagem de erro de validação que veio do FastAPI.

⚠️ Se houver erros:

  • apiRequest is not defined: Certifique-se de que você conectou api.js em index.html antes de app.js.
  • Verifique o console do navegador para outras ocorrências de erros de sintaxe em JavaScript.