Node.js

Design Patterns

Fonctions, classes, promesses et gestion d'erreurs en Node.js

Les design patterns sont des solutions éprouvées pour organiser et structurer votre code. Ce chapitre couvre les patterns fondamentaux utilisés dans les applications Node.js modernes.

Déclaration de fonctions

Fonction classique

Les fonctions classiques utilisent le mot-clé function et ont leur propre contexte this.

function calculateSum(a, b) {
  return a + b
}

console.log(calculateSum(5, 3)) // 8

Caractéristiques :

  • Possède son propre this
  • Peut être utilisée comme constructeur (new)
  • Hoisting : peut être appelée avant sa déclaration

Fonction fléchée (Arrow Function)

Introduite en ES6, la syntaxe fléchée est plus concise et n'a pas son propre contexte this.

Comparaison syntaxe et comportement :

// Arrow function (équivalent, plus court)
const calculateSum = (a, b) => a + b

console.log('Résultat:', calculateSum(5, 3)) // 8

Caractéristiques des arrow functions :

  • Pas de this propre (hérite du contexte parent)
  • Ne peut pas être utilisée comme constructeur (new)
  • Pas de hoisting : doit être déclarée avant utilisation
  • Syntaxe concise et moderne
  • Idéale pour les callbacks et fonctions courtes

Quand utiliser quoi ?

CasUtiliser
Callback simple (map, filter...)Arrow function
Méthode d'objet (besoin de this)Fonction classique
Fonction standaloneLes deux (préférer arrow)
Constructeur (avec new)Fonction classique

Classes vs Fichiers de fonctions

Approche fonctionnelle

Organisation du code avec des fonctions exportées.

math.js :

// math.js
export const add = (a, b) => {
  return a + b
}

export const subtract = (a, b) => {
  return a - b
}

export const multiply = (a, b) => {
  return a * b
}

export const divide = (a, b) => {
  if (b === 0) {
    throw new Error('Division par zéro impossible')
  }
  return a / b
}

Utilisation :

import { add, multiply, divide } from './math.js'

// Utilisation
console.log('Addition:', add(10, 5)) // 15
console.log('Multiplication:', multiply(10, 5)) // 50
console.log('Division:', divide(10, 5)) // 2

Avantages :

  • Simple et direct
  • Facile à tester (chaque fonction est isolée)
  • Pas de complexité liée aux classes
  • Difficile de partager un état (ex: historique des calculs)

Approche orientée objet (Classes)

Organisation du code avec des classes pour gérer un état partagé.

Calculator.js :

export class Calculator {
  constructor() {}

  add(a, b) {
    return a + b
  }

  subtract(a, b) {
    return a - b
  }

  multiply(a, b) {
    return a * b
  }

  divide(a, b) {
    if (b === 0) {
      throw new Error('Division par zéro impossible')
    }
    return a / b
  }
}

Utilisation :

import { Calculator } from './calculator.js'

// Instanciation
const calc = new Calculator()

// Utilisation
console.log(calc.add(10, 5)) // 15
console.log(calc.multiply(3, 4)) // 12
console.log(calc.add(7, 3)) // 10

Avantages :

  • Encapsulation (état partagé via this)
  • Méthodes qui accèdent au même état
  • Idéal quand on doit mémoriser des données (historique, configuration, etc.)

Quelle approche choisir ?

CritèreFonctionsClasses
Simplicité Plus simple Plus complexe
État partagé (historique, etc.) Difficile Facile (this)
Testabilité Excellent Bon
Organisation du code Plusieurs fichiers Tout dans une classe
Recommandation pour juniors Commencer par fonctionsUtiliser si besoin de mémoire

Promesses

Les promesses permettent de gérer les opérations asynchrones. Il existe deux façons de les consommer.

.then() / .catch()

// Créer une promesse qui se résout après un délai
const delay = () => {
  return new Promise((resolve, reject) => {
    // Simuler une opération asynchrone
    const success = true

    if (success) {
      resolve('Opération réussie !')
    } else {
      reject("Erreur lors de l'opération")
    }
  })
}

// :icon{name="i-heroicons-x-circle" class="text-red-500"} ANCIEN STYLE : .then() / .catch()
console.log("Début de l'opération...")
delay()
  .then((result) => {
    console.log('Résultat:', result)
    return 'Suite...'
  })
  .then((result2) => {
    console.log(result2)
  })
  .catch((error) => {
    console.error('Erreur:', error)
  })

Problèmes avec .then() / .catch() :

  • "Callback hell" : code difficile à lire avec beaucoup de .then() imbriqués
  • Gestion d'erreurs complexe (besoin de .catch() à chaque niveau)
  • Difficile de combiner avec des boucles ou conditions

async / await

// :icon{name="i-heroicons-check-circle" class="text-green-500"} STYLE MODERNE : async/await
const run = async () => {
  try {
    console.log("Début de l'opération...")
    const result = await delay()
    console.log('Résultat:', result)

    // Facile d'enchaîner plusieurs opérations
    const result2 = 'Suite...'
    console.log(result2)
  } catch (error) {
    console.error('Erreur:', error)
  }
}

run()

Avantages de async/await :

  • Code plus lisible (comme du code synchrone)
  • Gestion d'erreurs simple avec try/catch
  • Facile à combiner avec boucles et conditions

Exemple pratique : Requête API

// Fonction qui retourne une promesse
const fetchUser = async (userId) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/users/${userId}`,
  )
  return response.json()
}

const user = await fetchUser(1)
console.log('Utilisateur:', user)

Méthodes utiles :

MéthodeComportement
Promise.all()Attend que toutes les promesses réussissent
Promise.allSettled()Attend toutes les promesses (succès ou échec)
Promise.race()Retourne dès que la première promesse se termine
Promise.any()Retourne dès que la première promesse réussit

Gestion d'erreurs

// Fonction qui retourne une promesse
const fetchUser = async (userId) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/users/${userId}`,
  )

  // Vérifier si la requête a réussi
  if (!response.ok) {
    throw new Error(`Erreur HTTP: ${response.status}`) // Lance une erreur si le statut n'est pas OK
  }

  return response.json()
}

// Utilisation avec gestion d'erreur
try {
  const user = await fetchUser(1)
  console.log(':icon{name="i-heroicons-check-circle" class="text-green-500"} Utilisateur:', user)
} catch (error) {
  console.error(":icon{name="i-heroicons-x-circle" class="text-red-500"} Impossible de récupérer l'utilisateur:", error.message)
}

Le mot-clé throw :

  • throw permet de lancer une erreur manuellement
  • Quand vous utilisez throw, le code s'arrête immédiatement et passe au bloc catch
  • Utile pour créer des erreurs personnalisées avec des messages clairs
  • Dans l'exemple ci-dessus, on lance une erreur si le statut HTTP n'est pas OK (404, 500, etc.)

Points clés du try/catch :

  • Capture toutes les erreurs asynchrones (réseau, parsing JSON, erreurs lancées avec throw)
  • Code lisible et facile à maintenir
  • Fonctionne avec await uniquement (pas avec .then()/.catch())