Les 10 Erreurs JavaScript Que Même les Seniors Font
Closures, async/await, this... Les pièges classiques du JS et comment les éviter définitivement.

title: "Les 10 Erreurs JavaScript Que Même les Seniors Font" description: "Closures, async/await, this... Les pièges classiques du JS et comment les éviter définitivement." date: "2025-12-15" author: "Équipe Raicode" tags: ["JavaScript", "développement", "erreurs", "debugging", "bonnes pratiques"] category: "Développement" image: "/blog/erreurs-javascript-seniors-hero.png" ogImage: "/blog/erreurs-javascript-seniors-hero.png" keywords: ["erreurs JavaScript", "bugs JavaScript", "async await erreurs", "closures JavaScript", "this JavaScript"]
8 ans d'expérience JavaScript. Des centaines de code reviews. Et je vois toujours les mêmes erreurs, y compris dans du code écrit par des devs seniors.
Ce n'est pas une question de compétence. C'est que JavaScript a des pièges subtils qui demandent de la vigilance constante.
Voici les 10 erreurs les plus courantes - et comment les éviter.
Erreur #1 : Le this Perdu dans les Callbacks
Le Bug
class UserService {
constructor() {
this.users = [];
}
fetchUsers() {
fetch('/api/users')
.then(function(response) {
return response.json();
})
.then(function(data) {
this.users = data; // ❌ TypeError: Cannot set property 'users' of undefined
});
}
}
Pourquoi Ça Arrive
Dans une fonction classique, this dépend du contexte d'appel. Dans un callback, this n'est plus l'instance de la classe.
La Solution
// ✅ Solution 1 : Arrow functions (pas de this propre)
fetchUsers() {
fetch('/api/users')
.then(response => response.json())
.then(data => {
this.users = data; // ✅ this = l'instance
});
}
// ✅ Solution 2 : bind explicite
fetchUsers() {
fetch('/api/users')
.then(function(response) {
return response.json();
})
.then(function(data) {
this.users = data;
}.bind(this));
}
// ✅ Solution 3 : Variable de closure
fetchUsers() {
const self = this;
fetch('/api/users')
.then(function(response) {
return response.json();
})
.then(function(data) {
self.users = data;
});
}
Règle d'or : Utilisez les arrow functions pour les callbacks, sauf si vous avez besoin d'un this dynamique.
Erreur #2 : Async/Await dans une Boucle forEach
Le Bug
async function processUsers(userIds) {
userIds.forEach(async (id) => {
const user = await fetchUser(id); // ❌ N'attend pas !
console.log(user.name);
});
console.log('Terminé'); // S'affiche AVANT les users !
}
Pourquoi Ça Arrive
forEach ne sait pas gérer les promesses. Il lance tous les callbacks et continue immédiatement.
La Solution
// ✅ Solution 1 : for...of (séquentiel)
async function processUsers(userIds) {
for (const id of userIds) {
const user = await fetchUser(id);
console.log(user.name);
}
console.log('Terminé'); // S'affiche après tous les users
}
// ✅ Solution 2 : Promise.all (parallèle)
async function processUsers(userIds) {
const users = await Promise.all(
userIds.map(id => fetchUser(id))
);
users.forEach(user => console.log(user.name));
console.log('Terminé');
}
// ✅ Solution 3 : for await...of (streams)
async function processUsers(userIds) {
const promises = userIds.map(id => fetchUser(id));
for await (const user of promises) {
console.log(user.name);
}
}
Règle d'or : forEach + async = problème. Utilisez for...of ou Promise.all.
Erreur #3 : Comparaison avec == au lieu de ===
Le Bug
console.log(0 == ''); // true 😱
console.log(0 == false); // true 😱
console.log('' == false); // true 😱
console.log(null == undefined); // true 😱
console.log([1] == '1'); // true 😱
Pourquoi Ça Arrive
L'opérateur == fait de la coercition de type. JavaScript essaie de convertir les valeurs avant de comparer.
La Solution
// ✅ Toujours utiliser ===
console.log(0 === ''); // false
console.log(0 === false); // false
console.log('' === false); // false
console.log(null === undefined); // false
// ✅ Exception : vérifier null OU undefined
if (value == null) {
// value est null ou undefined
}
// ✅ Équivalent explicite
if (value === null || value === undefined) {
// ...
}
Règle d'or : Configurez ESLint avec eqeqeq: 'error' pour forcer ===.
Erreur #4 : Mutation d'Objets Par Référence
Le Bug
const user = { name: 'Alice', settings: { theme: 'dark' } };
const copy = user; // ❌ Ce n'est pas une copie !
copy.name = 'Bob';
console.log(user.name); // 'Bob' 😱
// Même avec spread...
const shallowCopy = { ...user };
shallowCopy.settings.theme = 'light';
console.log(user.settings.theme); // 'light' 😱 (shallow copy)
Pourquoi Ça Arrive
Les objets sont passés par référence. = copie la référence, pas l'objet. Le spread operator fait une copie superficielle.
La Solution
// ✅ Deep copy avec structuredClone (moderne)
const deepCopy = structuredClone(user);
deepCopy.settings.theme = 'light';
console.log(user.settings.theme); // 'dark' ✅
// ✅ Deep copy avec JSON (legacy, limité)
const deepCopy2 = JSON.parse(JSON.stringify(user));
// ⚠️ Ne fonctionne pas avec Date, Functions, undefined, etc.
// ✅ Immutabilité avec spread imbriqué
const updated = {
...user,
settings: {
...user.settings,
theme: 'light'
}
};
Règle d'or : Utilisez structuredClone() pour les deep copies. Préférez l'immutabilité.
Erreur #5 : Oublier le Return dans les Arrow Functions
Le Bug
// ❌ Oubli de return avec les accolades
const doubled = [1, 2, 3].map(n => {
n * 2; // Pas de return !
});
console.log(doubled); // [undefined, undefined, undefined]
// ❌ Return d'un objet mal formaté
const users = data.map(d => { name: d.name }); // ❌ Syntax error ou undefined
Pourquoi Ça Arrive
Avec {}, le return n'est pas implicite. Pour retourner un objet littéral, il faut des parenthèses.
La Solution
// ✅ Return implicite (sans accolades)
const doubled = [1, 2, 3].map(n => n * 2);
// ✅ Return explicite (avec accolades)
const doubled2 = [1, 2, 3].map(n => {
return n * 2;
});
// ✅ Return d'objet (parenthèses obligatoires)
const users = data.map(d => ({ name: d.name }));
// ✅ Multi-lignes avec return explicite
const processed = data.map(d => {
const name = d.name.toUpperCase();
const age = d.age + 1;
return { name, age };
});
Règle d'or : Arrow function + { = pensez à return. Objet littéral = ({ }).
Erreur #6 : Closure et Variables de Boucle
Le Bug
// ❌ Toutes les callbacks voient i = 3
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 3, 3, 3
}, 100);
}
Pourquoi Ça Arrive
var a une portée de fonction, pas de bloc. Au moment où les callbacks s'exécutent, la boucle est terminée et i vaut 3.
La Solution
// ✅ Solution 1 : let (portée de bloc)
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 0, 1, 2
}, 100);
}
// ✅ Solution 2 : IIFE (si var obligatoire)
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => {
console.log(j); // 0, 1, 2
}, 100);
})(i);
}
// ✅ Solution 3 : forEach avec index
[0, 1, 2].forEach((_, i) => {
setTimeout(() => {
console.log(i); // 0, 1, 2
}, 100);
});
Règle d'or : N'utilisez jamais var. Toujours let ou const.
Erreur #7 : Promesses Non Catchées
Le Bug
// ❌ Erreur silencieuse
async function fetchData() {
const response = await fetch('/api/data');
return response.json();
}
fetchData(); // Si erreur, rien ne se passe (ou UnhandledPromiseRejection)
// ❌ Catch qui ne catch pas
async function process() {
try {
fetchData(); // ❌ Oubli du await !
console.log('OK');
} catch (e) {
console.log('Erreur'); // Ne sera jamais appelé
}
}
Pourquoi Ça Arrive
Sans await, la promesse est lancée mais non attendue. Le try/catch ne peut pas intercepter une erreur asynchrone non awaited.
La Solution
// ✅ Toujours await les promesses dans try/catch
async function process() {
try {
await fetchData();
console.log('OK');
} catch (e) {
console.log('Erreur:', e.message);
}
}
// ✅ Handler global pour les promesses non catchées
window.addEventListener('unhandledrejection', event => {
console.error('Promesse non gérée:', event.reason);
// Envoyer à un service de monitoring
});
// ✅ Wrapper utilitaire
async function safeAsync(promise) {
try {
const data = await promise;
return [null, data];
} catch (error) {
return [error, null];
}
}
const [error, data] = await safeAsync(fetchData());
if (error) {
// Gérer l'erreur
}
Règle d'or : Chaque await devrait être dans un try/catch ou avoir un .catch().
Erreur #8 : Vérification de Valeurs Falsy
Le Bug
function greet(name) {
if (!name) {
name = 'Anonymous'; // ❌ Écrase les valeurs falsy valides
}
return `Hello, ${name}`;
}
greet(''); // 'Hello, Anonymous' - OK si voulu
greet(0); // 'Hello, Anonymous' - 😱 0 est falsy !
greet(false); // 'Hello, Anonymous' - 😱 false est falsy !
Pourquoi Ça Arrive
En JavaScript, 0, '', false, null, undefined, et NaN sont tous falsy.
La Solution
// ✅ Vérification explicite de null/undefined
function greet(name) {
if (name === null || name === undefined) {
name = 'Anonymous';
}
return `Hello, ${name}`;
}
// ✅ Nullish coalescing (??)
function greet(name) {
const displayName = name ?? 'Anonymous';
// ?? ne remplace que null et undefined, pas 0 ou ''
return `Hello, ${displayName}`;
}
// ✅ Paramètre par défaut
function greet(name = 'Anonymous') {
return `Hello, ${name}`;
}
// ⚠️ Attention : ne s'applique que si undefined, pas null
// ✅ Optional chaining + nullish coalescing
const userName = user?.profile?.name ?? 'Anonymous';
Règle d'or : Utilisez ?? au lieu de || quand 0 ou '' sont des valeurs valides.
Erreur #9 : Typeof et Arrays
Le Bug
console.log(typeof []); // 'object' 😱
console.log(typeof null); // 'object' 😱
console.log(typeof NaN); // 'number' 😱
console.log(typeof new Date()); // 'object'
// ❌ Mauvaise vérification
if (typeof data === 'object') {
data.forEach(item => {}); // TypeError si data est null ou un objet
}
Pourquoi Ça Arrive
typeof est limité. C'est un vestige des débuts de JavaScript.
La Solution
// ✅ Vérifier un array
Array.isArray([]); // true
Array.isArray({}); // false
Array.isArray('string'); // false
// ✅ Vérifier null
value === null;
// ✅ Vérifier NaN
Number.isNaN(NaN); // true
Number.isNaN('NaN'); // false (contrairement à isNaN global)
// ✅ Vérifier un type précis
Object.prototype.toString.call([]); // '[object Array]'
Object.prototype.toString.call(null); // '[object Null]'
Object.prototype.toString.call(new Date()); // '[object Date]'
// ✅ Fonction utilitaire
function getType(value) {
if (value === null) return 'null';
if (Array.isArray(value)) return 'array';
return typeof value;
}
Règle d'or : Array.isArray() pour les tableaux, === null pour null.
Erreur #10 : Event Listeners Non Nettoyés
Le Bug
// ❌ Memory leak
function setupComponent() {
window.addEventListener('resize', handleResize);
document.addEventListener('click', handleClick);
}
// Appelé plusieurs fois sans cleanup
setupComponent();
setupComponent();
setupComponent();
// 3 listeners pour chaque événement ! 😱
// ❌ Dans React (class components)
componentDidMount() {
window.addEventListener('resize', this.handleResize);
}
// Oubli du componentWillUnmount = memory leak
Pourquoi Ça Arrive
Les event listeners persistent tant qu'ils ne sont pas explicitement retirés. Chaque ajout crée une nouvelle référence.
La Solution
// ✅ Cleanup explicite
function setupComponent() {
const handleResize = () => { /* ... */ };
const handleClick = () => { /* ... */ };
window.addEventListener('resize', handleResize);
document.addEventListener('click', handleClick);
// Retourner une fonction de cleanup
return () => {
window.removeEventListener('resize', handleResize);
document.removeEventListener('click', handleClick);
};
}
const cleanup = setupComponent();
// Plus tard...
cleanup();
// ✅ AbortController (moderne)
const controller = new AbortController();
window.addEventListener('resize', handleResize, { signal: controller.signal });
document.addEventListener('click', handleClick, { signal: controller.signal });
// Cleanup en une ligne
controller.abort();
// ✅ React useEffect
useEffect(() => {
const handleResize = () => { /* ... */ };
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
Règle d'or : Chaque addEventListener doit avoir son removeEventListener correspondant.
La Checklist Anti-Bugs
□ Utilisez des arrow functions pour les callbacks
□ N'utilisez jamais forEach avec async/await
□ Toujours === sauf pour null check
□ structuredClone() pour les deep copies
□ Parenthèses pour retourner un objet: () => ({ })
□ let/const, jamais var
□ try/catch autour de chaque await
□ ?? au lieu de || pour les valeurs falsy
□ Array.isArray() pour vérifier les tableaux
□ Cleanup des event listeners
Le Mot de la Fin
Ces erreurs ne sont pas des signes d'incompétence. JavaScript est un langage avec des comportements parfois surprenants.
La différence entre un junior et un senior ? Le senior connaît ces pièges et met en place des garde-fous (linting, tests, code reviews).
Configurez ESLint correctement. La moitié de ces erreurs sont détectables automatiquement.
Besoin d'un audit de qualité de code ? On review votre codebase et on identifie les problèmes potentiels. Discutons-en
Prêt à lancer votre projet ?
Transformez vos idées en réalité avec un développeur passionné par la performance et le SEO. Discutons de votre projet dès aujourd'hui.


