Requêtes réseau et programmation asynchrone

Programmation web - Client riche

Objectifs

  • Comprendre comment envoyer une requêtes à un serveur et recevoir la réponse associée via JS
  • Apprendre à lire la réponse reçue et exploiter les données qui s'y trouvent
  • Refléter l'état d'une requête asynchrone dans l'UI
  • Comprendre les bases de la programmation asynchrone en JS

Les requêtes asynchrones

Les requêtes bloquantes

Les requêtes non-bloquantes (1)

Les requêtes non-bloquantes (2)

Ces requêtes sont souvent appelées « requêtes AJAX » (pour Asynchronous Javascript And XML) ou XHR (pour « XMLHttpRequest », l'objet historique sous-jacent). Toutefois, les données reçues en réponse ne sont pas forcément du XML. On utilise même plutôt JSON la plupart du temps.

JSON (pour JavaScript Object Notation) est un format de données textuelles qui s'inspire de la syntaxe des objets en JS. Voir la page Wikipédia correspondante pour plus d'informations

L'API fetch

const result = fetch("url/to/resource")
console.log(result)

fetch : présentation

La fonction fetch permet d'envoyer une requête HTTP de manière non-bloquante. Elle retourne une Promise résolue avec un objet de type Response

Attends, une Pr... quoi ?!

Quand on écrit un bout de code, on s'attend à ce que les lignes soient exécutées les une après les autres, dans l'ordre. On dit que le code est "synchrone" : une ligne n'est exécutée qu'une fois que la ligne précédente a terminé son exécution

const a = 10
const b = 42
const result = add(a, b)
console.log(result)
// => 52

Code synchrone vs code asynchrone

Mais du code synchrone ne permet pas de paralléliser deux bouts de code qui n'ont pas besoin l'un de l'autre

/*
 * getCategories n'est exécutée qu'après que getProducts ait
 * terminé son exécution.
 * Pourtant, les 2 fonctions ne sont pas correlées, donc on
 * pourrait imaginer de les exécuter en parallèle
 */
const products = getProducts()
showProducts(products)

const categories = getCategories()
showCategories(categories)

Code synchrone vs code asynchrone

Dans un langage comme Java, on aurait tendance à lancer 2 threads qui pourraient faire les traitements en parallèle.

Code synchrone vs code asynchrone

Mais...

JavaScript est monothreadé.

De plus, dans le navigateur, notre code JS tourne dans le même thread que toute l'interface du navigateur. Donc un bout de code "bloquant" bloque tout le navigateur

L'event loop (1)

L'objet Promise

L'objet Promise représente la "promesse" qu'un appel asynchrone va produire un résultat (valeur ou erreur) dans le futur.

state: pending
value: undefined
state: fulfilled
value: 42
resolve(42)
state: rejected
reason: error
reject(error)

async / await

Le mot-clef async

Le mot-clef async est lié à la définition d'une fonction. Il définit la fonction comme étant asynchrone.

Cela a deux implications :

  • La fonction retourne toujours une Promise
    • Fulfilled avec la valeur de retour
    • Rejected avec une erreur levée
  • Il est possible d'utiliser le mot-clef await dans la fonction
const asyncFunction = async () => {
  /* Utilisation d'APIs asynchrones */
  
  return 42
}

const promise = asyncFunction()

Le mot-clef await

Le mot-clef await s'utilise sur une Promise. Il permet de "mettre en pause" la fonction dans laquelle il se trouve, jusqu'à ce que la Promise soit fulfilled ou rejected.

Il ne peut être utilisé que dans une fonction asynchrone

const asyncFunction = async () => {
  console.log("Before")
  await somethingReturningAPromise()
  console.log("After")
}
try {
  const response = await fetch("url/to/resource")
  const data = await response.json()
  console.log(data)
} catch (err) {
  console.error(error)
}

fetch : récupérer le corps de la réponse

Le serveur peut nous renvoyer une réponse dans divers formats. Il faut qu'on formate la réponse dans le format attendu

Une fois que le corps de la réponse a été formaté, on ne peut pas le formater dans un autre format

const response = await fetch("url/to/resource", {
  headers: {
    Authentication: "authentication token"
  }
})

fetch : définir les headers de la requête

Attention, tous les headers ne sont pas autorisés : voir la liste des headers interdits dans la spécification

const response = await fetch("url/to/resource", {
  method: "POST" // "GET" ou "POST" ou "PUT" ou "DELETE"
})

fetch : définir la méthode de la requête

const response = await fetch("url/to/resource", {
  method: "POST",
  body: "some data"
})

fetch : définir le corps de la requête (1)

Pour envoyer des données dans le corps de la requête, on peut passer une chaîne de caractères au paramètre body de la requête

Le header Content-Type est défini à "text/plain;charset=UTF-8" par défaut

const response = await fetch("url/to/resource", {
  method: "POST",
  headers: {
    "Content-Type": "application/json;charset=utf-8"
  },
  body: JSON.stringify({ firstname: "Cyrille", role: "Prof" })
})

fetch : définir le corps de la requête (2)

On peut envoyer les données à partir d'un objet JS en le transformant en JSON

Le header Content-Type doit être défini à "application/json;charset=utf-8" dans ce cas, pour indiquer au serveur que le corps de la requête contient du JSON

const form = document.querySelector("form")
const data = new FormData(form)

const response = await fetch("url/to/resource", {
  method: "POST",
  body: data
})

fetch : définir le corps de la requête (3)

On peut aussi utiliser un objet FormData pour récupérer les données contenus dans un formulaire et les envoyer

Lorsqu'on utilise un objet FormData dans le corps de la requête, le header Content-Type est ajouté automatiquement

fetch : exemple d'envoi de données d'un formulaire

async/await : exemple

async/await : exemple

Récap ! (1)

  • JavaScript est monothreadé, mais le concept d'event loop lui permet de faire des appels à des API asynchrones
  • async/await permet, en s'appuyant sur les promesses, d'écrire du code asynchrone de la même manière que du code synchrone
  • Un cas d'usage majeur de la programmation asynchrone en JS côté client est d'envoyer des requêtes HTTP asynchrones à un serveur

Récap ! (2)

  • Pour envoyer une requête asynchrone, on utilise la fonction fetch
  • La fonction fetch renvoie une Promise qui sera résolue avec un objet de type Response
  • La réponse reçue doit être formatée avant de pouvoir en utiliser le corps
  • On peut aussi envoyer des données dans le corps d'une requête : soit via une chaîne de caractères, soit via un objet FormData

Des questions ?

Programmation web - client riche - La programmation asynchrone en JS

By Cyrille Perois

Programmation web - client riche - La programmation asynchrone en JS

  • 1,065