Qu’est-ce qu’une fonction async ?

Bon, en parlant des nombreux avantages de la syntaxe async/await, on a déjà commencé à toucher à divers points tant de async que de await.

Rentrons à présent plus dans le détail, en commençant par explorer ce qu'est au juste une fonction async, et comment elle fonctionne.

Renvoie implicitement une promesse

Pour commencer, je le repète, une fonction async renvoie une promesse. C’est intrinsèque et automatique.  Comportementalement, ça revient à peu près à mettre son corps dans un return Promise.resolve().then().

Du coup, si une exception y est levée et non rattrapée, elle renverra une promesse rejetée. Mais faute d'exception, son éventuelle valeur renvoyée sera la valeur d'accomplissement de sa promesse.

Remarquez qu'on peut qualifier async aussi bien une fonction définie avec le traditionnel mot-clé function qu'une fonction fléchée. Il n'y a aucune différence d'impact entre les deux cas.

Exemple de code 04-async-functions-return-promises.js

Pour bien enfoncer le clou, regardez ce code : alors même que la fonction sayWhat() se contente de renvoyer son argument, l'appeler produit une promesse, comme en atteste notre premier console.log().

On peut donc appeler then() dessus, très classiquement.

Idem pour une levée d'exception, qui donnera une promesse rejetée, exploitable par exemple avec catch() plus bas dans la chaîne.

Alors oui, sayWhat() n'a aucune raison d'avoir un qualificateur async sur ce coup. Mais ça illustre bien notre propos.

Permet await… mais dans le corps immédiat

Lorsqu'une fonction est qualifiée async, elle permet, syntaxiquement, le recours au mot-clé await dans son corps immédiat.

J'insiste sur cette distinction : le mot-clé await ne peut pas apparaître n'importe où « à l'intérieur » de la fonction async. Notamment, il n'est pas transitivement autorisé dans les fonctions imbriquées, telles que des callbacks ou des déclarations de fonctions locales.

Pour le dire autrement, await n'est autorisé que si la fonction immédiatement englobante est qualifiée async.

Exemple de code 05-async-scopes.js

 Ce petit code illustre ça : on a ici un await non pas dans le corps immédiat de thisCanWait(), mais dans un callback à l'intérieur, callback qui n'est pas, lui, déclaré async (et il n'a pas à l'être, ça ne ferait pas ce qu'on veut).

On est là en présence d'une erreur de syntaxe, ce qui veut dire que le fichier entier refusera de s'exécuter, on aura donc même pas le premier log « c'est parti », voyez plutôt (démo)

Pas d'await ? Pas d'async.

Par réciproque, on devine qu'il ne sert à rien de qualifier async une fonction qui n'utilise pas await.

Si c'est juste pour vous éviter un appel à Promise.resolve() dans le return, c'est un peu foireux, franchement, et ça rend le code déroutant.

En prime, si votre fonction renvoie déjà une promesse, ça va juste l'enrober dans une deuxième. Ça marche, mais c'est inutilement gras. On aura des règles de linter contre ça.

Exemple de code 06-awaitless-async-is-useless.js

Ce petit code montre que oui (démo à la volée), on peut qualifier une fonction async sans recourir à await, mais encore une fois, c'est alourdir le code et son exécution pour rien.