En aquest tema, explorarem patrons avançats per gestionar operacions asíncrones en TypeScript. Aquests patrons són útils per escriure codi més net, eficient i mantenible quan es treballa amb operacions asíncrones complexes.

Contingut

  1. Patró de Retry (Reintentar)
  2. Patró de Throttling (Limitació)
  3. Patró de Debouncing (Desacceleració)
  4. Patró de Circuit Breaker (Interruptor de Circuit)
  5. Patró de Queues (Cues)

  1. Patró de Retry (Reintentar)

El patró de Retry és útil quan una operació asíncrona pot fallar temporalment i volem reintentar-la un nombre determinat de vegades abans de donar-nos per vençuts.

Exemple

function retry<T>(fn: () => Promise<T>, retries: number): Promise<T> {
    return fn().catch(err => {
        if (retries > 0) {
            return retry(fn, retries - 1);
        } else {
            throw err;
        }
    });
}

// Exemple d'ús
const fetchData = () => fetch('https://api.example.com/data').then(response => response.json());

retry(fetchData, 3)
    .then(data => console.log(data))
    .catch(err => console.error('Failed after 3 retries:', err));

Explicació

  • retry: Funció que accepta una funció asíncrona fn i un nombre de reintents retries.
  • catch: Si la promesa falla, es reintenta fins que el nombre de reintents s'esgoti.

  1. Patró de Throttling (Limitació)

El patró de Throttling limita el nombre de vegades que una funció pot ser executada en un període de temps determinat.

Exemple

function throttle<T>(fn: (...args: any[]) => Promise<T>, limit: number): (...args: any[]) => Promise<T> {
    let inThrottle: boolean;
    return function(...args: any[]): Promise<T> {
        if (!inThrottle) {
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
            return fn(...args);
        } else {
            return Promise.reject('Throttled');
        }
    };
}

// Exemple d'ús
const fetchDataThrottled = throttle(fetchData, 2000);

fetchDataThrottled()
    .then(data => console.log(data))
    .catch(err => console.error(err));

Explicació

  • throttle: Funció que accepta una funció asíncrona fn i un límit de temps limit.
  • inThrottle: Variable que controla si la funció està en estat de limitació.

  1. Patró de Debouncing (Desacceleració)

El patró de Debouncing assegura que una funció només s'executi després d'un període de temps determinat des de l'última vegada que va ser invocada.

Exemple

function debounce<T>(fn: (...args: any[]) => Promise<T>, delay: number): (...args: any[]) => Promise<T> {
    let timeoutId: NodeJS.Timeout;
    return function(...args: any[]): Promise<T> {
        clearTimeout(timeoutId);
        return new Promise((resolve, reject) => {
            timeoutId = setTimeout(() => {
                fn(...args).then(resolve).catch(reject);
            }, delay);
        });
    };
}

// Exemple d'ús
const fetchDataDebounced = debounce(fetchData, 2000);

fetchDataDebounced()
    .then(data => console.log(data))
    .catch(err => console.error(err));

Explicació

  • debounce: Funció que accepta una funció asíncrona fn i un retard delay.
  • timeoutId: Variable que emmagatzema l'identificador del temporitzador.

  1. Patró de Circuit Breaker (Interruptor de Circuit)

El patró de Circuit Breaker evita que una aplicació intenti realitzar operacions que probablement fallaran, basant-se en l'historial d'errors recents.

Exemple

class CircuitBreaker {
    private failureCount: number = 0;
    private successCount: number = 0;
    private state: 'CLOSED' | 'OPEN' | 'HALF-OPEN' = 'CLOSED';
    private readonly failureThreshold: number;
    private readonly successThreshold: number;
    private readonly timeout: number;

    constructor(failureThreshold: number, successThreshold: number, timeout: number) {
        this.failureThreshold = failureThreshold;
        this.successThreshold = successThreshold;
        this.timeout = timeout;
    }

    async execute<T>(fn: () => Promise<T>): Promise<T> {
        if (this.state === 'OPEN') {
            throw new Error('Circuit is open');
        }

        try {
            const result = await fn();
            this.onSuccess();
            return result;
        } catch (err) {
            this.onFailure();
            throw err;
        }
    }

    private onSuccess() {
        this.successCount++;
        if (this.state === 'HALF-OPEN' && this.successCount >= this.successThreshold) {
            this.state = 'CLOSED';
            this.successCount = 0;
        }
    }

    private onFailure() {
        this.failureCount++;
        if (this.failureCount >= this.failureThreshold) {
            this.state = 'OPEN';
            setTimeout(() => {
                this.state = 'HALF-OPEN';
                this.failureCount = 0;
            }, this.timeout);
        }
    }
}

// Exemple d'ús
const circuitBreaker = new CircuitBreaker(3, 2, 5000);

circuitBreaker.execute(fetchData)
    .then(data => console.log(data))
    .catch(err => console.error(err));

Explicació

  • CircuitBreaker: Classe que implementa el patró de Circuit Breaker.
  • execute: Mètode que executa una funció asíncrona i gestiona l'estat del circuit.

  1. Patró de Queues (Cues)

El patró de Queues gestiona una cua de tasques asíncrones, assegurant que només un nombre determinat de tasques s'executin simultàniament.

Exemple

class AsyncQueue {
    private queue: (() => Promise<any>)[] = [];
    private running: number = 0;
    private readonly concurrency: number;

    constructor(concurrency: number) {
        this.concurrency = concurrency;
    }

    enqueue(task: () => Promise<any>) {
        this.queue.push(task);
        this.runNext();
    }

    private runNext() {
        if (this.running >= this.concurrency || this.queue.length === 0) {
            return;
        }

        const task = this.queue.shift();
        if (task) {
            this.running++;
            task().then(() => {
                this.running--;
                this.runNext();
            }).catch(() => {
                this.running--;
                this.runNext();
            });
        }
    }
}

// Exemple d'ús
const queue = new AsyncQueue(2);

queue.enqueue(() => fetchData().then(data => console.log(data)));
queue.enqueue(() => fetchData().then(data => console.log(data)));
queue.enqueue(() => fetchData().then(data => console.log(data)));

Explicació

  • AsyncQueue: Classe que implementa una cua de tasques asíncrones amb un límit de concurrència.
  • enqueue: Mètode que afegeix una tasca a la cua i inicia l'execució si és possible.

Conclusió

En aquest tema, hem explorat diversos patrons avançats per gestionar operacions asíncrones en TypeScript. Aquests patrons ajuden a escriure codi més robust i mantenible quan es treballa amb operacions asíncrones complexes. Practicar aquests patrons i aplicar-los en projectes reals millorarà significativament la qualitat del teu codi asíncron.

© Copyright 2024. Tots els drets reservats