Actualités

Démystifier la fonction defer()

Laravel Jutsu avatar
Publié le 16 octobre 2024
Couverture de l'article Démystifier la fonction defer()

Qu'est-ce que c'est ?

Si vous n’avez pas suivi l'actualité des réseaux sociaux récemment, vous êtes peut-être passé à côté de l’effervescence autour de la fonction defer() !

Introduite avec la version 11.23, defer() s’impose déjà comme un outil incontournable dans l’écosystème Laravel. Bien que son fonctionnement soit simple, sa force réside dans sa capacité à différer l'exécution d'un code jusqu'à ce qu'une réponse réussie soit envoyée.

Grâce à cette fonctionnalité, vous pouvez déléguer des tâches gourmandes en ressources (comme des appels à des API externes) à un callback, tout en renvoyant une réponse rapide, optimisant ainsi la performance globale et l'expérience utilisateur.

1Route::get('/the-longest-hello-world', function () {
2 defer(function () {
3 // 💤
4 sleep(60);
5 });
6 
7 return "Hello Laravel France!";
8});

Comme vous pouvez le constater, defer() prend en paramètre une fonction callback qui sera exécutée après l'envoi de la réponse. Même si cette fonction met 10 secondes à s'exécuter, la réponse sera immédiatement envoyée à l'utilisateur.

Exécution différée permanente

Par défaut, la fonction callback différée ne sera pas exécutée si la requête retourne un code HTTP de type 4xx ou 5xx. Ce comportement peut être modifié en passant true comme troisième paramètre à defer() ou en appelant la méthode always() sur l'objet retourné.

Cela garantit l'exécution de la fonction callback, quel que soit le statut de la réponse.

1Route::get('/the-worst-hello-world', function () {
2 defer(function () {
3 // 💤
4 sleep(60);
5 })->always();
6 
7 throw new Exception('Oops!');
8 
9 return "Hello Laravel France!";
10});

Contexte d'utilisation

Il est essentiel de comprendre dans quels contextes defer() est applicable. Cette fonction ne se limite pas aux requêtes HTTP : il permet aussi d’exécuter son callback après une commande ou un Job réussi.

Contrairement à dispatch()->afterResponse() qui n'intervient que dans le cadre des requêtes HTTP, defer() s'adapte à plusieurs scénarios.

Explorons les coulisses

Plongeons dans les détails techniques pour mieux comprendre. Spoiler : c'est plus simple qu'il n'y paraît.

Un point intéressant, Taylor Otwell, créateur de Laravel, a souligné que defer() ne repose ni sur des files d'attente, ni sur des tâches asynchrones complexes.

1function defer(?callable $callback = null, ?string $name = null, bool $always = false)
2{
3 if ($callback === null) {
4 return app(DeferredCallbackCollection::class);
5 }
6 
7 return tap(
8 new DeferredCallback($callback, $name, $always),
9 fn ($deferred) => app(DeferredCallbackCollection::class)[] = $deferred
10 );
11}

On crée une instance de la classe DeferredCallback, puis on l'ajoute à DeferredCallbackCollection, résolue par le Service Container de Laravel.

Ainsi, toutes les fonctions callback différées sont stockées dans un tableau et seront exécutées à la fin de la requête.

You Are Terminated.

Pour les passionnés de middlewares, vous remarquerez un nouvel ajout : \Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class.

La méthode terminate() des middlewares s’exécute après que la réponse a été envoyée à l’utilisateur.

1public function terminate(Request $request, Response $response)
2{
3 Container::getInstance()
4 ->make(DeferredCallbackCollection::class)
5 ->invokeWhen(fn ($callback) => $response->getStatusCode() < 400 || $callback->always);
6}

Pour faire simple, Laravel s'appuie sur le protocole FastCGI, qui maintient les processus PHP actifs après l'envoi d'une réponse. Cela permet à Laravel d'exécuter du code additionnel, comme la méthode terminate(), une fois la réponse transmise.

Cette méthode appelle la classe DeferredCallbackCollection, qui regroupe tous les callbacks différés de la requête.

La méthode invokeWhen() détermine si ces callbacks doivent être exécutés, en fonction du code de réponse (inférieur à 400) ou si la propriété always est activée. C’est ainsi que Laravel gère l’exécution différée avec le helper defer().

La façade Concurrency

Laravel propose également une nouvelle façade appelée Concurrency, qui permet d'exécuter plusieurs callbacks en parallèle :

1Concurrency::run([
2 fn () => GenerateUltraHeavyCalculationsAction($data),
3 fn () => GenerateMegaHeavyCalculationsAction($data),
4]);

Et bonne nouvelle : si vous souhaitez décaler l'exécution du code, vous pouvez toujours utiliser defer, qui réutilise ce même helper.

1Concurrency::defer([
2 fn () => GenerateUltraHeavyCalculationsAction($data),
3 fn () => GenerateMegaHeavyCalculationsAction($data),
4]);

Conclusion

Laravel continue de se réinventer, en mettant un point d’honneur à simplifier la vie des développeurs. Le helper defer() n'est qu'un exemple parmi tant d'autres.

Cependant, attention à ne pas confondre son usage avec celui des Jobs. defer() est idéal pour des tâches courtes mais lourdes, comme logger une action ou exécuter de petites analyses après une requête.

En revanche, pour des tâches complexes ou nécessitant du débogage, il est préférable d'utiliser les files d'attentes !

A lire

Autres articles de la même catégorie