Manual de Promesas, Async/Await y Consumo de REST API con JWT

Promesas en JavaScript

Las promesas representan un valor que puede estar disponible ahora, en el futuro o nunca.

Creación de una promesa

const miPromesa = new Promise((resolve, reject) => {
    const exito = true; // Simulamos condición
    
    if (exito) {
        resolve('Operación exitosa!');
    } else {
        reject('Algo salió mal');
    }
});

Consumo de promesas

miPromesa
    .then((resultado) => {
        console.log(resultado); // 'Operación exitosa!'
    })
    .catch((error) => {
        console.error(error); // 'Algo salió mal'
    })
    .finally(() => {
        console.log('La promesa finalizó');
    });
Características de las promesas:

Async/Await

Async/Await es una forma más moderna y legible de trabajar con código asíncrono.

Función async

async function obtenerDatos() {
    try {
        const respuesta = await fetch('https://api.example.com/data');
        const datos = await respuesta.json();
        console.log(datos);
        return datos;
    } catch (error) {
        console.error('Error al obtener datos:', error);
        throw error;
    }
}

Uso de async/await

// Dentro de una función async
async function procesarDatos() {
    try {
        const datos = await obtenerDatos();
        console.log('Datos procesados:', datos);
    } catch (error) {
        console.error('Error en procesamiento:', error);
    }
}

// Llamada a la función
procesarDatos();
Importante: await solo puede usarse dentro de funciones marcadas con async.

Consumo de REST API

Fetch API básico

async function fetchData(url) {
    try {
        const response = await fetch(url);
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        return await response.json();
    } catch (error) {
        console.error('Error al obtener datos:', error);
        throw error;
    }
}

// Uso
fetchData('https://api.example.com/posts')
    .then(data => console.log(data))
    .catch(error => console.error(error));

Clase para manejar API

class ApiClient {
    constructor(baseURL) {
        this.baseURL = baseURL;
        this.token = null;
    }

    async request(endpoint, options = {}) {
        const url = `${this.baseURL}${endpoint}`;
        
        const headers = {
            'Content-Type': 'application/json',
            ...options.headers
        };
        
        if (this.token) {
            headers['Authorization'] = `Bearer ${this.token}`;
        }

        const config = {
            ...options,
            headers
        };

        try {
            const response = await fetch(url, config);
            
            if (!response.ok) {
                const errorData = await response.json();
                throw new Error(errorData.message || 'Error en la solicitud');
            }
            
            return await response.json();
        } catch (error) {
            console.error('Error en la solicitud:', error);
            throw error;
        }
    }

    // Métodos específicos
    async get(endpoint) {
        return this.request(endpoint);
    }

    async post(endpoint, body) {
        return this.request(endpoint, {
            method: 'POST',
            body: JSON.stringify(body)
        });
    }

    // ...otros métodos (put, delete, etc)
}

// Uso
const api = new ApiClient('https://api.example.com');
api.get('/posts').then(data => console.log(data));

Autenticación con JWT

Login y almacenamiento de token

class AuthService {
    constructor() {
        this.token = localStorage.getItem('jwt') || null;
    }

    async login(credentials) {
        try {
            const response = await fetch('https://api.example.com/auth/login', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(credentials)
            });
            
            if (!response.ok) {
                throw new Error('Credenciales inválidas');
            }
            
            const data = await response.json();
            this.setToken(data.token);
            return data;
        } catch (error) {
            console.error('Error en login:', error);
            throw error;
        }
    }

    setToken(token) {
        this.token = token;
        localStorage.setItem('jwt', token);
    }

    logout() {
        this.token = null;
        localStorage.removeItem('jwt');
    }

    isAuthenticated() {
        return !!this.token;
    }

    // Verificar si el token está expirado
    isTokenExpired() {
        if (!this.token) return true;
        
        try {
            const payload = JSON.parse(atob(this.token.split('.')[1]));
            return payload.exp * 1000 < Date.now();
        } catch (error) {
            console.error('Error al decodificar token:', error);
            return true;
        }
    }
}

Interceptor para refrescar token

class ApiClientWithAuth extends ApiClient {
    constructor(baseURL, authService) {
        super(baseURL);
        this.authService = authService;
    }

    async request(endpoint, options = {}) {
        // Verificar si el token está expirado
        if (this.authService.isTokenExpired()) {
            try {
                // Lógica para refrescar el token
                const newToken = await this.refreshToken();
                this.authService.setToken(newToken);
            } catch (error) {
                this.authService.logout();
                throw new Error('Sesión expirada, por favor inicie sesión nuevamente');
            }
        }

        return super.request(endpoint, options);
    }

    async refreshToken() {
        const response = await fetch('https://api.example.com/auth/refresh', {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${this.authService.token}`
            }
        });
        
        if (!response.ok) {
            throw new Error('No se pudo refrescar el token');
        }
        
        const data = await response.json();
        return data.token;
    }
}

Ejemplo Completo: Aplicación con JWT

Configuración inicial

// auth.js
class AuthService {
    // ...implementación anterior
}

// api.js
class ApiClient {
    // ...implementación anterior
}

class ApiClientWithAuth extends ApiClient {
    // ...implementación anterior
}

// Instancias globales
const authService = new AuthService();
const api = new ApiClientWithAuth('https://api.example.com', authService);

Flujo de autenticación

// Login
async function handleLogin(email, password) {
    try {
        const userData = await authService.login({ email, password });
        console.log('Login exitoso:', userData);
        return userData;
    } catch (error) {
        console.error('Error en login:', error);
        throw error;
    }
}

// Obtener datos protegidos
async function getProtectedData() {
    if (!authService.isAuthenticated()) {
        throw new Error('No autenticado');
    }

    try {
        const data = await api.get('/protected-route');
        console.log('Datos protegidos:', data);
        return data;
    } catch (error) {
        console.error('Error al obtener datos protegidos:', error);
        throw error;
    }
}

// Logout
function handleLogout() {
    authService.logout();
    console.log('Sesión cerrada');
}

Mejores Prácticas

Para promesas y async/await:
Para JWT y APIs: