Vous utilisez les fonctions régulièrement et le JavaScript commence à dévoiler ses secrets pour vous.

Que vous soyez développeur sur un autre langage ou simplement un débutant du code qui veut se perfectionner, les callbacks sont courants en JavaScript !

Il est donc nécessaire que vous connaissiez bien ce principe et comment les utiliser.

On va voir ce qu'est une callback, comment on l'utilise, comment on l'écrit et quelques bonnes pratiques d'utilisation.

Pour les débutants, le concept de callback est généralement perturbant maiiiiis rassurez-vous, il faut bien commencer quelque part.

Avec un peu de pratique, ce sera de l'histoire ancienne !!

Rappels

En JavaScript, les fonctions sont des objets. Cette propriété est importante pour comprendre les sujets les plus avancées en JS. Comme n'importe quel objet, une fonction peut être assignée à une variable, peut être passée en paramètre d'une autre fonction et peut être retournée comme résultat d'une autre fonction !

L'objet fonction contient une string (chaîne de caractère) du code de cette fonction.

Une Callback

Une callback est une fonction donnée en paramètre d'une autre fonction.

La fonction qui demande en paramètre une callback la déclenchera lorsque ce sera nécessaire.

//	Une fonction qui demande une autre fonction en paramètre
function sayHello(callback) {
	console.log("Bonjour !");
	//	Exécution de la fonction en paramètre
	callback();
}

//	Exécution de la fonction avec une autre fonction en paramètre
sayHello(function() {
	console.log("Callback !")
});

Console :

Ce concept est simple à maîtriser, on peut pousser encore plus loin !

Imaginons qu'on souhaite avoir une fonction qui permet d'effectuer une opération arithmétique sur deux nombres : doMaths.

function doMaths(n1, n2, callback) {
	//	on doit écrire ici après
}

//	On éxécute la function avec :
//	n1 : 1
//	n2 : 1
//  callback : une fonction qui reçoit 2 paramètres et retourne une addition
var addition = doMaths(1, 1, function(a, b) {
	return a + b;
});


//	On éxécute la function avec :
//	n1 : 1
//	n2 : 1
//  callback : une fonction qui reçoit 2 paramètres et retourne une soustraction
var soustraction = doMaths(1, 1, function(a, b) {
	return a - b;
});

console.log("Addition", addition);
console.log("Soustraction", soustraction);

Du fait que le concept de callback est une fonction en paramètre d'une autre fonction, on pourrait remplir la fonction doMaths avec un bout de code qui effectuerait une opération générique de mathématique.

function doMaths(n1, n2, callback) {
	var result = callback(n1, n2);
	return result;
}


var addition = doMaths(1, 1, function(a, b) {
	return a + b;
});

var soustraction = doMaths(1, 1, function(a, b) {
	return a - b;
});

console.log("Addition", addition);
console.log("Soustraction", soustraction);

Console :

Voici le lien pour exécuter vous même ce code, http://jsbin.com/hijazitoju/edit?js,console

Utilisation de fonctions nommées

Pour améliorer la lisibilité de votre code, vous devriez assigner à une variable la fonction de callback ou la nommer.

function doMaths(n1, n2, callback) {
	var result = callback(n1, n2);
	return result;
}

//	Comme ça
var doAddition = function(a, b) {
	return a + b;
}

//	Et ça !
var doSoustration = function(a, b) {
	return a - b;
}

var addition = doMaths(1, 1, doAddition);

var soustraction = doMaths(1, 1, doSoustration);

console.log("Addition", addition);
console.log("Soustraction", soustraction);

Amusez-vous à pratiquer avec le code ici : http://jsbin.com/coporeqete/1/edit?js,console

De cette manière, votre code sera plus facile à composer.

Utilisation de fonctions anonymes

Parfois, selon votre cas ou votre façon de coder, l'utilisation de fonctions nommées peut être très rébarbative, surtout pour un usage unique !

function doMaths(n1, n2, callback) {
	var result = callback(n1, n2);
	return result;
}

//	fonction anonymes
var addition = doMaths(1, 1, function(a, b) {
	return a + b;
});

//	fonction arrow (fléchée ? ahah)
var soustraction = doMaths(1, 1, (a, b) => {
	return a - b;
});

console.log("Addition", addition);
console.log("Soustraction", soustraction);

Concernant le block-scope des fonctions scopé ou arrow, nous verrons ça une autre fois dans un autre article.

Gestion des erreurs

Un des standards issus de la pratique des callbacks dans l'industrie concerne la gestion des erreurs. Que faire lorsque notre fonction peut gérer une erreur et en être notifiée ?

La règle est simple : le premier paramètre de la callback sera un objet pour une erreur éventuelle et les suivants seront les résultats.

function doAddition(n1, n2, callback) {
	if (
		(typeof n1 != "number")
		||
		(typeof n2 != "number")
	) {
		callback(
			new Error("L'un des deux paramètres n'est pas un nombre"),
			null
		);
	} else {
      callback(null, n1 + n2);
    }
}

doAddition(1, 1, (error, result) => {
	if (!error) {
		console.log("Addition", result);
	} else {
		console.error(error);
	}
});

doAddition(1, "test", (error, result) => {
	if (!error) {
		console.log("Addition", result);
	} else {
		console.error(error);
	}
});

Console :

Encore une fois, pour pratiquer et exécuter ce code : http://jsbin.com/vocihivawi/1/edit?js,console

Callback Hell

Il existe un problème récurrent, que je vois un peu trop à mon goût dans l'industrie, concernant les callbacks.

Lorsqu'on veut enchaîner les callbacks les unes après les autres, le code finit rapidement par devenir illisible.

createUser({ name: "david" }, (errorCreate, resultCreate) => {
	if (!errorCreate) {
		saveUser(resultCreate, (errorSave, resultSave) => {
			if (!errorSave) {
				sendEmail(resultSave, (errorEmail, resultEmail) => {
					if (!errorEmail) {
						// on va arrêter ici, hein ?
					}
				});
			}
		});
	}
});

Comme on peut le voir, avec déjà 3 niveaux de callbacks qui s'enchaînent, on commence déjà à avoir les yeux qui piquent.

Petite anecdote, j'ai déjà vu une succession de plus de 60 callbacks sur un serveur d'un client... Autant vous dire que j'ai dû réécrire au propre. Si le concerné voit cet article, partage-le :p

La solution ? Il y en a pleins !

Nous pouvons tout simplement suivre ces deux règles :

1 - Nommez vos fonctions, déclarez-les et passez juste le nom de la fonction comme callback, au lieu de définir une fonction anonyme dans le paramètre de la fonction principale.

2 - Modularité: séparez votre code en modules, de sorte que vous puissiez exporter une section de code qui effectue un travail particulier. Vous pouvez ensuite importer ce module dans votre application plus grande.

Nous pourrions écrire des fonctions en amont pour éviter le callback hell.

function initialisationUserCreationDone(user) {
	console.log("La création de l'utilisateur ", user.name, " est finie");
}

function prepareSendEmailToUser(user) {
	sendEmail(user, (error, result) => {
		if (!error) {
			initialisationUserCreationDone(user);
		} else {
			onErrorCreation(error);
		}
	});
}

function prepareUserSave(user) {
	saveUser(user, (error, result) => {
		if (!error) {
			prepareSendEmailToUser(result);
		} else {
			onErrorCreation(error);
		}
	});
}

function prepareUserCreation(user) {
	createUser(user, (error, result) => {
		if (!error) {
			prepareUserSave(result);
		} else {
			onErrorCreation(error);
		}
	});
}

function onErrorCreation(error) {
	console.error(error);
}

prepareUserCreation({ name: "David" });

C'est quand même plus propre, non ?

Nous verrons des alternatives aux callbacks prochainnement comme : les fonctions asynchrones, les Promises et async.js.

Petits mots

Les callbacks JavaScript sont merveilleuses et puissantes à utiliser, elles offrent de grands avantages pour vos applications Web et votre code.

Vous devriez les utiliser quand le besoin s'en fait sentir.

Recherchez des façons de refactoriser votre code pour l'abstraction, la maintenabilité et la lisibilité avec les callbacks.

function userInteraction(article) {

	const { user, david } = article.humans;

	if (user.likeArticle) {
		if (!user.isFanFacebookPage) {
			user.likeFacebookPage();
		}
		user.shareFacebook();
		user.shareTwitter();
		user.shareLinkedin();
		david.notifyThankYou();
	} else {
		david.noOffence();
		david.notifyStillLoveYou();
	}

}

userInteraction(this);
Partages ! 😉