Fonctions flechees et WebSockets en JavaScript : guide complet avec exemples pratiques

Apprenez a utiliser les fonctions flechees et WebSockets en JavaScript. Connexions bidirectionnelles, messages binaires et securite HTTPS.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 12 min read
Fonctions flechees et WebSockets en JavaScript : guide complet avec exemples pratiques

Fonctions Fléchées et WebSockets en JavaScript : Guide Complet

Introduction

L’arrivée d’ECMAScript 6 (ES6) en 2015 a marqué un tournant majeur dans l’évolution de JavaScript. Parmi les nombreuses fonctionnalités introduites, les fonctions fléchées (arrow functions) se distinguent comme l’une des plus utilisées par les développeurs modernes. Leur syntaxe concise et leur comportement particulier avec le mot-clé this en font un outil indispensable dans le développement JavaScript contemporain.

Parallèlement, les WebSockets ont révolutionné la communication en temps réel sur le web. Contrairement au protocole HTTP traditionnel qui fonctionne sur un modèle requête-réponse, les WebSockets établissent une connexion bidirectionnelle persistante entre le client et le serveur. Cette technologie est devenue essentielle pour les applications nécessitant des mises à jour en temps réel : messageries instantanées, tableaux de bord en direct, jeux en ligne, et bien plus encore.

Dans cet article approfondi, nous allons explorer en détail ces deux concepts fondamentaux du JavaScript moderne. Vous découvrirez non seulement comment les utiliser, mais aussi leurs subtilités, leurs pièges courants, et les meilleures pratiques pour les intégrer efficacement dans vos projets.

Pourquoi combiner ces deux sujets ?

Les fonctions fléchées et les WebSockets se complètent parfaitement dans le contexte de la programmation événementielle. Les gestionnaires d’événements WebSocket bénéficient grandement de la syntaxe concise des arrow functions, et la gestion du contexte this devient beaucoup plus intuitive. Comprendre ces deux concepts ensemble vous permettra d’écrire du code plus élégant et maintenable.


Les Fonctions Fléchées en Profondeur

Les fonctions fléchées, introduites avec ES6, offrent une syntaxe plus courte pour écrire des expressions de fonction. Mais au-delà de la syntaxe, elles présentent des différences comportementales importantes par rapport aux fonctions traditionnelles.

Syntaxe Complète des Arrow Functions

Syntaxe de base avec parenthèses

La forme la plus courante utilise des parenthèses pour encadrer les paramètres :

// Fonction avec plusieurs paramètres
const add = (a, b) => a + b;
console.log(add(2, 3)); // 5

// Fonction sans paramètre (parenthèses obligatoires)
const sayHello = () => "Bonjour !";
console.log(sayHello()); // "Bonjour !"

// Fonction avec un seul paramètre (parenthèses optionnelles)
const double = (x) => x * 2;
const doubleAlt = x => x * 2; // Même chose, sans parenthèses
console.log(double(5)); // 10

Syntaxe sans parenthèses (un seul paramètre)

Quand la fonction n’a qu’un seul paramètre, les parenthèses sont optionnelles :

const increment = n => n + 1;
const greet = name => `Bonjour, ${name} !`;
const isEven = num => num % 2 === 0;

console.log(increment(5));      // 6
console.log(greet("Alice"));    // "Bonjour, Alice !"
console.log(isEven(4));         // true

Syntaxe avec accolades (corps de fonction)

Pour des fonctions plus complexes nécessitant plusieurs instructions, on utilise des accolades :

const calculateTax = (price, taxRate) => {
    const tax = price * taxRate;
    const total = price + tax;
    return total;
};

console.log(calculateTax(100, 0.2)); // 120

// Fonction avec logique conditionnelle
const getDiscount = (amount, isVIP) => {
    if (isVIP) {
        return amount * 0.2; // 20% de réduction
    }
    return amount * 0.1; // 10% de réduction
};

Retour Implicite vs Retour Explicite

L’une des caractéristiques les plus appréciées des arrow functions est le retour implicite.

Retour implicite (sans accolades)

// Le résultat de l'expression est automatiquement retourné
const square = x => x * x;
const multiply = (a, b) => a * b;
const getFullName = (first, last) => `${first} ${last}`;

// Retour implicite d'un objet (nécessite des parenthèses)
const createUser = (name, age) => ({ name, age });
console.log(createUser("Alice", 25)); // { name: "Alice", age: 25 }

// Attention : sans parenthèses, les accolades sont interprétées comme un bloc
const badCreateUser = (name, age) => { name, age }; // Retourne undefined !

Retour explicite (avec accolades)

// Avec des accolades, le return est obligatoire
const calculateArea = (width, height) => {
    const area = width * height;
    return area; // return explicite nécessaire
};

// Sans return, la fonction retourne undefined
const brokenFunction = (x) => {
    x * 2; // Pas de return = undefined retourné
};
console.log(brokenFunction(5)); // undefined

Le this Lexical : Différence Fondamentale

C’est la différence la plus importante entre les fonctions fléchées et les fonctions traditionnelles. Les arrow functions n’ont pas leur propre this : elles héritent du this du contexte englobant.

// Problème avec une fonction traditionnelle
const timer = {
    seconds: 0,
    start: function() {
        setInterval(function() {
            this.seconds++; // 'this' fait référence à l'objet global (window), pas à timer
            console.log(this.seconds); // NaN ou erreur
        }, 1000);
    }
};

// Solution avec une fonction fléchée
const timerFixed = {
    seconds: 0,
    start: function() {
        setInterval(() => {
            this.seconds++; // 'this' fait référence à timerFixed
            console.log(this.seconds); // 1, 2, 3, ...
        }, 1000);
    }
};

// Exemple avec des événements DOM
class Button {
    constructor(label) {
        this.label = label;
        this.clickCount = 0;
    }

    // Méthode avec fonction fléchée pour le callback
    attachTo(element) {
        element.addEventListener('click', () => {
            this.clickCount++; // 'this' fait référence à l'instance Button
            console.log(`${this.label} cliqué ${this.clickCount} fois`);
        });
    }
}

Absence de l’objet arguments

Les fonctions fléchées n’ont pas accès à l’objet arguments. Utilisez les paramètres rest à la place :

// Fonction traditionnelle avec arguments
function traditionalSum() {
    let sum = 0;
    for (let i = 0; i < arguments.length; i++) {
        sum += arguments[i];
    }
    return sum;
}
console.log(traditionalSum(1, 2, 3, 4)); // 10

// Arrow function : arguments n'existe pas
const arrowSum = () => {
    // console.log(arguments); // ReferenceError: arguments is not defined
};

// Solution : utiliser les paramètres rest
const restSum = (...numbers) => {
    return numbers.reduce((acc, num) => acc + num, 0);
};
console.log(restSum(1, 2, 3, 4)); // 10

// Paramètres rest avec d'autres paramètres
const logMessages = (prefix, ...messages) => {
    messages.forEach(msg => console.log(`${prefix}: ${msg}`));
};
logMessages("[INFO]", "Démarrage", "Connexion", "Prêt");

Les Arrow Functions ne peuvent pas être des Constructeurs

Les fonctions fléchées ne peuvent pas être utilisées avec le mot-clé new :

// Fonction traditionnelle comme constructeur
function Person(name) {
    this.name = name;
}
const alice = new Person("Alice"); // OK
console.log(alice.name); // "Alice"

// Arrow function : ne peut pas être un constructeur
const PersonArrow = (name) => {
    this.name = name;
};
// const bob = new PersonArrow("Bob"); // TypeError: PersonArrow is not a constructor

// Utilisez des classes ou des fonctions traditionnelles pour les constructeurs
class PersonClass {
    constructor(name) {
        this.name = name;
    }
}
const charlie = new PersonClass("Charlie"); // OK

Tableau Comparatif : Fonction Classique vs Arrow Function

CaractéristiqueFonction ClassiqueArrow Function
Syntaxefunction(a, b) { return a + b; }(a, b) => a + b
Binding de thisDynamique (dépend de l’appel)Lexical (hérite du parent)
Objet argumentsDisponibleNon disponible
Peut être constructeurOui (new Fn())Non
Méthode call/apply/bindChange le thisNe change pas le this
HoistingOui (déclarations)Non
Nom de fonctionfunction maFn() {}Anonyme (sauf assignation)
Utilisation recommandéeMéthodes d’objet, constructeursCallbacks, expressions courtes

Les WebSockets en Profondeur

Architecture Client-Serveur Bidirectionnelle

Les WebSockets établissent une connexion persistante bidirectionnelle entre le client et le serveur. Contrairement à HTTP où le client doit initier chaque communication, avec WebSocket le serveur peut envoyer des données au client à tout moment.

// Architecture de base
/*
    Client                              Serveur
       |                                    |
       |------ Handshake HTTP Upgrade ---->|
       |<----- HTTP 101 Switching ---------|
       |                                    |
       |====== Connexion WebSocket ========|
       |                                    |
       |<------- Message serveur ----------|
       |-------- Message client ---------->|
       |<------- Message serveur ----------|
       |-------- Message client ---------->|
       |                                    |
       |------ Fermeture connexion ------->|
       |<----- Confirmation fermeture -----|
*/

Comparaison HTTP vs WebSocket

// HTTP : Requête-Réponse (polling)
async function httpPolling() {
    while (true) {
        const response = await fetch('/api/updates');
        const data = await response.json();
        processUpdates(data);
        await sleep(5000); // Attendre 5 secondes avant la prochaine requête
    }
}

// WebSocket : Connexion persistante
function websocketConnection() {
    const ws = new WebSocket('wss://example.com/updates');
    ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        processUpdates(data); // Réception instantanée des mises à jour
    };
}

États de Connexion WebSocket

Chaque WebSocket possède un état de connexion accessible via la propriété readyState :

const ws = new WebSocket('wss://example.com/socket');

// Les quatre états possibles
console.log(WebSocket.CONNECTING); // 0 - Connexion en cours
console.log(WebSocket.OPEN);       // 1 - Connexion établie
console.log(WebSocket.CLOSING);    // 2 - Fermeture en cours
console.log(WebSocket.CLOSED);     // 3 - Connexion fermée

// Vérifier l'état avant d'envoyer
const sendMessage = (message) => {
    if (ws.readyState === WebSocket.OPEN) {
        ws.send(message);
        return true;
    } else {
        console.warn(`Impossible d'envoyer, état: ${ws.readyState}`);
        return false;
    }
};

// Attendre que la connexion soit ouverte
const waitForConnection = () => {
    return new Promise((resolve, reject) => {
        if (ws.readyState === WebSocket.OPEN) {
            resolve();
        } else if (ws.readyState === WebSocket.CONNECTING) {
            ws.addEventListener('open', resolve);
            ws.addEventListener('error', reject);
        } else {
            reject(new Error('WebSocket fermé ou en cours de fermeture'));
        }
    });
};

Les Quatre Événements WebSocket

onopen - Connexion établie

const ws = new WebSocket('wss://example.com/socket');

ws.onopen = (event) => {
    console.log('Connexion WebSocket établie');
    console.log('Protocole utilisé:', ws.protocol);

    // Envoyer un message d'authentification initial
    ws.send(JSON.stringify({
        type: 'auth',
        token: 'mon-token-jwt'
    }));
};

// Ou avec addEventListener
ws.addEventListener('open', (event) => {
    console.log('Connecté au serveur');
});

onmessage - Réception de données

ws.onmessage = (event) => {
    console.log('Message reçu:', event.data);

    // Traiter différents types de données
    if (typeof event.data === 'string') {
        try {
            const json = JSON.parse(event.data);
            handleJsonMessage(json);
        } catch (e) {
            handleTextMessage(event.data);
        }
    } else if (event.data instanceof Blob) {
        handleBlobMessage(event.data);
    } else if (event.data instanceof ArrayBuffer) {
        handleBinaryMessage(event.data);
    }
};

// Gestionnaires par type de message
const handleJsonMessage = (data) => {
    switch (data.type) {
        case 'notification':
            showNotification(data.payload);
            break;
        case 'update':
            updateUI(data.payload);
            break;
        case 'error':
            handleError(data.payload);
            break;
        default:
            console.log('Type de message inconnu:', data.type);
    }
};

onerror - Gestion des erreurs

ws.onerror = (event) => {
    console.error('Erreur WebSocket:', event);

    // Noter que l'événement error ne contient pas beaucoup d'informations
    // La plupart des détails sont dans l'événement close qui suit
};

// Gestion d'erreur plus complète
const handleWebSocketError = (error) => {
    // Notifier l'utilisateur
    showErrorToast('Problème de connexion au serveur');

    // Logger pour le débogage
    console.error('WebSocket error:', {
        readyState: ws.readyState,
        url: ws.url,
        error: error
    });

    // Tenter une reconnexion
    scheduleReconnection();
};

ws.onerror = handleWebSocketError;

onclose - Fermeture de connexion

ws.onclose = (event) => {
    console.log('Connexion WebSocket fermée');
    console.log('Code:', event.code);
    console.log('Raison:', event.reason);
    console.log('Fermeture propre:', event.wasClean);

    // Codes de fermeture courants
    switch (event.code) {
        case 1000:
            console.log('Fermeture normale');
            break;
        case 1001:
            console.log('Le serveur s\'arrête ou le navigateur quitte la page');
            break;
        case 1006:
            console.log('Connexion fermée anormalement (pas de handshake de fermeture)');
            scheduleReconnection();
            break;
        case 1011:
            console.log('Erreur serveur inattendue');
            break;
        default:
            console.log('Code de fermeture:', event.code);
    }
};

Envoi de Données : Texte, JSON et Binaire

// Envoi de texte simple
ws.send('Hello, serveur !');

// Envoi de données JSON
const sendJson = (data) => {
    ws.send(JSON.stringify(data));
};

sendJson({
    type: 'message',
    content: 'Bonjour tout le monde !',
    timestamp: Date.now(),
    userId: 12345
});

// Envoi de données binaires avec ArrayBuffer
const sendBinary = (buffer) => {
    if (ws.readyState === WebSocket.OPEN) {
        ws.send(buffer);
    }
};

// Exemple : envoyer des coordonnées en binaire
const sendCoordinates = (x, y, z) => {
    const buffer = new ArrayBuffer(12); // 3 floats * 4 bytes
    const view = new DataView(buffer);
    view.setFloat32(0, x, true); // little-endian
    view.setFloat32(4, y, true);
    view.setFloat32(8, z, true);
    ws.send(buffer);
};

// Envoi de Blob (fichiers, images)
const sendFile = async (file) => {
    const arrayBuffer = await file.arrayBuffer();
    ws.send(arrayBuffer);
};

// Configuration du type de données binaires reçues
ws.binaryType = 'arraybuffer'; // ou 'blob'

Heartbeat/Ping-Pong pour Maintenir la Connexion

Les connexions WebSocket peuvent être fermées par des proxies ou pare-feux inactifs. Le pattern ping-pong maintient la connexion active :

class WebSocketWithHeartbeat {
    constructor(url) {
        this.url = url;
        this.ws = null;
        this.heartbeatInterval = null;
        this.heartbeatTimeout = null;
        this.HEARTBEAT_INTERVAL = 30000; // 30 secondes
        this.HEARTBEAT_TIMEOUT = 10000;  // 10 secondes pour répondre
    }

    connect() {
        this.ws = new WebSocket(this.url);

        this.ws.onopen = () => {
            console.log('Connecté, démarrage du heartbeat');
            this.startHeartbeat();
        };

        this.ws.onmessage = (event) => {
            const data = JSON.parse(event.data);

            if (data.type === 'pong') {
                this.handlePong();
            } else {
                this.handleMessage(data);
            }
        };

        this.ws.onclose = () => {
            this.stopHeartbeat();
            console.log('Connexion fermée, arrêt du heartbeat');
        };
    }

    startHeartbeat() {
        this.heartbeatInterval = setInterval(() => {
            this.sendPing();
        }, this.HEARTBEAT_INTERVAL);
    }

    stopHeartbeat() {
        if (this.heartbeatInterval) {
            clearInterval(this.heartbeatInterval);
            this.heartbeatInterval = null;
        }
        if (this.heartbeatTimeout) {
            clearTimeout(this.heartbeatTimeout);
            this.heartbeatTimeout = null;
        }
    }

    sendPing() {
        if (this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify({ type: 'ping', timestamp: Date.now() }));

            // Définir un timeout pour la réponse pong
            this.heartbeatTimeout = setTimeout(() => {
                console.warn('Pas de pong reçu, fermeture de la connexion');
                this.ws.close();
            }, this.HEARTBEAT_TIMEOUT);
        }
    }

    handlePong() {
        // Réponse pong reçue, annuler le timeout
        if (this.heartbeatTimeout) {
            clearTimeout(this.heartbeatTimeout);
            this.heartbeatTimeout = null;
        }
        console.log('Pong reçu, connexion active');
    }

    handleMessage(data) {
        // Traiter les autres messages
        console.log('Message reçu:', data);
    }
}

Pattern de Reconnexion Automatique

class ReconnectingWebSocket {
    constructor(url, options = {}) {
        this.url = url;
        this.options = {
            maxRetries: options.maxRetries || 10,
            initialDelay: options.initialDelay || 1000,
            maxDelay: options.maxDelay || 30000,
            backoffMultiplier: options.backoffMultiplier || 1.5,
            ...options
        };

        this.ws = null;
        this.retryCount = 0;
        this.currentDelay = this.options.initialDelay;
        this.isIntentionallyClosed = false;
        this.messageQueue = [];

        // Callbacks personnalisables
        this.onopen = () => {};
        this.onmessage = () => {};
        this.onclose = () => {};
        this.onerror = () => {};
    }

    connect() {
        this.isIntentionallyClosed = false;
        this.ws = new WebSocket(this.url);

        this.ws.onopen = (event) => {
            console.log('WebSocket connecté');
            this.retryCount = 0;
            this.currentDelay = this.options.initialDelay;

            // Envoyer les messages en attente
            this.flushMessageQueue();

            this.onopen(event);
        };

        this.ws.onmessage = (event) => {
            this.onmessage(event);
        };

        this.ws.onerror = (event) => {
            console.error('Erreur WebSocket');
            this.onerror(event);
        };

        this.ws.onclose = (event) => {
            console.log(`WebSocket fermé (code: ${event.code})`);
            this.onclose(event);

            if (!this.isIntentionallyClosed) {
                this.scheduleReconnection();
            }
        };
    }

    scheduleReconnection() {
        if (this.retryCount >= this.options.maxRetries) {
            console.error('Nombre maximum de tentatives atteint');
            return;
        }

        console.log(`Reconnexion dans ${this.currentDelay}ms (tentative ${this.retryCount + 1}/${this.options.maxRetries})`);

        setTimeout(() => {
            this.retryCount++;
            this.currentDelay = Math.min(
                this.currentDelay * this.options.backoffMultiplier,
                this.options.maxDelay
            );
            this.connect();
        }, this.currentDelay);
    }

    send(data) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(typeof data === 'string' ? data : JSON.stringify(data));
        } else {
            // Mettre en file d'attente si pas connecté
            this.messageQueue.push(data);
            console.log('Message mis en file d\'attente');
        }
    }

    flushMessageQueue() {
        while (this.messageQueue.length > 0) {
            const message = this.messageQueue.shift();
            this.send(message);
        }
    }

    close() {
        this.isIntentionallyClosed = true;
        if (this.ws) {
            this.ws.close(1000, 'Fermeture volontaire');
        }
    }
}

// Utilisation
const ws = new ReconnectingWebSocket('wss://example.com/socket', {
    maxRetries: 5,
    initialDelay: 1000,
    maxDelay: 16000
});

ws.onmessage = (event) => {
    console.log('Message:', event.data);
};

ws.connect();

Cas d’Usage WebSocket

1. Chat en Temps Réel

class ChatClient {
    constructor(serverUrl, userId) {
        this.ws = new ReconnectingWebSocket(serverUrl);
        this.userId = userId;
        this.messageHandlers = [];
    }

    connect() {
        this.ws.onopen = () => {
            // Authentification à la connexion
            this.ws.send({
                type: 'auth',
                userId: this.userId
            });
        };

        this.ws.onmessage = (event) => {
            const message = JSON.parse(event.data);
            this.handleMessage(message);
        };

        this.ws.connect();
    }

    handleMessage(message) {
        switch (message.type) {
            case 'chat_message':
                this.displayMessage(message);
                break;
            case 'user_joined':
                this.notifyUserJoined(message.userId);
                break;
            case 'user_left':
                this.notifyUserLeft(message.userId);
                break;
            case 'typing':
                this.showTypingIndicator(message.userId);
                break;
        }
    }

    sendMessage(roomId, content) {
        this.ws.send({
            type: 'chat_message',
            roomId,
            content,
            timestamp: Date.now()
        });
    }

    sendTypingIndicator(roomId) {
        this.ws.send({
            type: 'typing',
            roomId
        });
    }

    joinRoom(roomId) {
        this.ws.send({
            type: 'join_room',
            roomId
        });
    }

    displayMessage(message) {
        console.log(`[${message.userId}]: ${message.content}`);
    }

    notifyUserJoined(userId) {
        console.log(`${userId} a rejoint le chat`);
    }

    notifyUserLeft(userId) {
        console.log(`${userId} a quitté le chat`);
    }

    showTypingIndicator(userId) {
        console.log(`${userId} est en train d'écrire...`);
    }
}

2. Notifications Push en Temps Réel

class NotificationService {
    constructor(serverUrl) {
        this.ws = new ReconnectingWebSocket(serverUrl);
        this.notificationHandlers = new Map();
    }

    connect(authToken) {
        this.ws.onopen = () => {
            this.ws.send({
                type: 'subscribe',
                token: authToken,
                channels: ['orders', 'messages', 'alerts']
            });
        };

        this.ws.onmessage = (event) => {
            const notification = JSON.parse(event.data);
            this.handleNotification(notification);
        };

        this.ws.connect();
    }

    handleNotification(notification) {
        // Afficher notification native si supporté
        if (Notification.permission === 'granted') {
            new Notification(notification.title, {
                body: notification.body,
                icon: notification.icon
            });
        }

        // Appeler les handlers enregistrés
        const handlers = this.notificationHandlers.get(notification.channel) || [];
        handlers.forEach(handler => handler(notification));
    }

    on(channel, handler) {
        if (!this.notificationHandlers.has(channel)) {
            this.notificationHandlers.set(channel, []);
        }
        this.notificationHandlers.get(channel).push(handler);
    }
}

// Utilisation
const notifications = new NotificationService('wss://api.example.com/notifications');

notifications.on('orders', (notif) => {
    updateOrderBadge(notif.count);
});

notifications.on('messages', (notif) => {
    updateMessageBadge(notif.count);
});

notifications.connect('jwt-token-here');

3. Jeux Multijoueurs

class GameClient {
    constructor(serverUrl) {
        this.ws = new ReconnectingWebSocket(serverUrl);
        this.gameState = {};
        this.playerId = null;
    }

    connect(playerName) {
        this.ws.onopen = () => {
            this.ws.send({
                type: 'join_game',
                playerName
            });
        };

        this.ws.onmessage = (event) => {
            const message = JSON.parse(event.data);
            this.handleGameMessage(message);
        };

        this.ws.connect();
    }

    handleGameMessage(message) {
        switch (message.type) {
            case 'game_state':
                this.updateGameState(message.state);
                break;
            case 'player_move':
                this.animatePlayerMove(message.playerId, message.position);
                break;
            case 'player_joined':
                this.addPlayer(message.player);
                break;
            case 'player_left':
                this.removePlayer(message.playerId);
                break;
            case 'game_over':
                this.showGameOver(message.winner);
                break;
        }
    }

    sendMove(direction) {
        this.ws.send({
            type: 'player_move',
            direction,
            timestamp: Date.now()
        });
    }

    sendAction(action) {
        this.ws.send({
            type: 'player_action',
            action,
            timestamp: Date.now()
        });
    }

    updateGameState(state) {
        this.gameState = state;
        this.render();
    }

    render() {
        // Rendu du jeu basé sur gameState
        console.log('État du jeu:', this.gameState);
    }
}

4. Trading et Données Financières en Temps Réel

class TradingClient {
    constructor(serverUrl) {
        this.ws = new ReconnectingWebSocket(serverUrl);
        this.subscriptions = new Set();
        this.priceHandlers = new Map();
    }

    connect(apiKey) {
        this.ws.onopen = () => {
            this.ws.send({
                type: 'authenticate',
                apiKey
            });

            // Réabonner aux symboles après reconnexion
            this.subscriptions.forEach(symbol => {
                this.subscribe(symbol);
            });
        };

        this.ws.onmessage = (event) => {
            const data = JSON.parse(event.data);
            this.handleMarketData(data);
        };

        this.ws.connect();
    }

    subscribe(symbol) {
        this.subscriptions.add(symbol);
        this.ws.send({
            type: 'subscribe',
            symbol
        });
    }

    unsubscribe(symbol) {
        this.subscriptions.delete(symbol);
        this.ws.send({
            type: 'unsubscribe',
            symbol
        });
    }

    handleMarketData(data) {
        switch (data.type) {
            case 'price_update':
                this.updatePrice(data.symbol, data.price, data.change);
                break;
            case 'order_book':
                this.updateOrderBook(data.symbol, data.bids, data.asks);
                break;
            case 'trade':
                this.recordTrade(data);
                break;
        }
    }

    onPriceUpdate(symbol, handler) {
        if (!this.priceHandlers.has(symbol)) {
            this.priceHandlers.set(symbol, []);
        }
        this.priceHandlers.get(symbol).push(handler);
    }

    updatePrice(symbol, price, change) {
        const handlers = this.priceHandlers.get(symbol) || [];
        handlers.forEach(handler => handler({ price, change }));
    }

    placeOrder(symbol, side, quantity, price) {
        this.ws.send({
            type: 'place_order',
            symbol,
            side, // 'buy' ou 'sell'
            quantity,
            price,
            timestamp: Date.now()
        });
    }
}

// Utilisation
const trading = new TradingClient('wss://trading.example.com/ws');

trading.onPriceUpdate('BTC/USD', ({ price, change }) => {
    document.getElementById('btc-price').textContent = `$${price.toFixed(2)}`;
    document.getElementById('btc-change').textContent = `${change > 0 ? '+' : ''}${change.toFixed(2)}%`;
});

trading.connect('api-key');
trading.subscribe('BTC/USD');
trading.subscribe('ETH/USD');

Bonnes Pratiques

Pour les Fonctions Fléchées

  1. Utilisez-les pour les callbacks courts : Les arrow functions brillent dans les callbacks de méthodes comme map, filter, reduce.

  2. Évitez-les pour les méthodes d’objet : Préférez la syntaxe de méthode ES6 pour les objets littéraux.

  3. Attention au retour implicite d’objets : N’oubliez pas les parenthèses autour des objets retournés.

  4. Ne les utilisez pas comme constructeurs : Utilisez des classes ou des fonctions traditionnelles.

  5. Profitez du this lexical : C’est leur principal avantage pour les callbacks et les événements.

Pour les WebSockets

  1. Toujours gérer la reconnexion : Les connexions WebSocket peuvent être interrompues pour diverses raisons.

  2. Implémenter un heartbeat : Maintenez la connexion active avec des pings réguliers.

  3. Utiliser wss:// en production : Toujours sécuriser les connexions WebSocket avec TLS.

  4. Gérer la file d’attente des messages : Stockez les messages envoyés pendant la déconnexion.

  5. Valider les données reçues : Ne faites jamais confiance aux données provenant du serveur sans validation.

  6. Limiter les reconnexions : Utilisez un backoff exponentiel pour éviter de surcharger le serveur.


Pièges Courants

Fonctions Fléchées

  1. Oublier que this est lexical : Ne pas utiliser bind, call, ou apply pour changer le contexte.
// Ce code ne fonctionne pas comme prévu
const obj = {
    value: 42,
    getValue: () => this.value // 'this' n'est pas 'obj' !
};
console.log(obj.getValue()); // undefined
  1. Retour implicite avec accolades : Les accolades créent un bloc, pas un objet.
const bad = () => { name: 'test' }; // Retourne undefined
const good = () => ({ name: 'test' }); // Retourne { name: 'test' }
  1. Utiliser arguments inexistant : Utilisez les paramètres rest à la place.

WebSockets

  1. Envoyer avant que la connexion soit ouverte : Toujours vérifier readyState.
// Mauvais
const ws = new WebSocket(url);
ws.send('message'); // Erreur possible !

// Bon
ws.onopen = () => ws.send('message');
  1. Ne pas gérer les erreurs et fermetures : Toujours implémenter onerror et onclose.

  2. Oublier de fermer proprement : Appelez ws.close() quand vous n’avez plus besoin de la connexion.

  3. Ne pas parser correctement les données : Toujours utiliser try-catch pour JSON.parse.

ws.onmessage = (event) => {
    try {
        const data = JSON.parse(event.data);
        handleData(data);
    } catch (e) {
        console.error('JSON invalide reçu:', event.data);
    }
};

Conclusion

Les fonctions fléchées et les WebSockets sont deux piliers du JavaScript moderne qui, lorsqu’ils sont maîtrisés, permettent de créer des applications web puissantes et réactives.

Tableau Récapitulatif

ConceptPoints ClésCas d’Usage
Arrow FunctionsSyntaxe concise, this lexical, pas de argumentsCallbacks, méthodes de tableau, gestionnaires d’événements
WebSocketsBidirectionnel, temps réel, connexion persistanteChat, notifications, jeux, trading
HeartbeatMaintient la connexion, détecte les déconnexionsToute application WebSocket en production
Reconnexion AutoBackoff exponentiel, file d’attenteApplications critiques nécessitant une haute disponibilité

En combinant ces deux technologies, vous pouvez créer des applications web modernes qui offrent une expérience utilisateur fluide et réactive. Les fonctions fléchées simplifient votre code de gestion d’événements WebSocket, tandis que les WebSockets ouvrent la porte à des fonctionnalités en temps réel qui étaient auparavant difficiles ou impossibles à implémenter.

Prochain article :

  • Décryptage des Promesses (Promises) et async/await en JavaScript

Liens utiles :

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles