Tous les événements socketio entrants commenceront à être traités dans l'ordre dans lequel ils arrivent. Ceux qui disposent de fonctions de gestion synchrones sont assurés de terminer le traitement dans le même ordre. Toutefois, cela peut ne pas être le cas pour les événements dotés de gestionnaires asynchrones, qui peuvent terminer le traitement dans n'importe quel ordre. Ce comportement rend notre code plus rapide, mais dans certains cas, cela peut ne pas être ce que nous souhaitons.
Dans ce court article, vous apprendrez comment faire démarrer et terminer le traitement des événements avec des tâches asynchrones dans l'ordre dans lequel ils arrivent sur le serveur. Pour y parvenir, nous allons créer un système de file d’attente personnalisé simple.
Plongeons-nous.
Supposons que vous ayez une situation dans laquelle deux clients sont connectés à votre serveur socketio. Vous souhaitez un comportement dans lequel le premier à envoyer un événement crée une nouvelle ligne dans une table de base de données et le second met à jour cette même ligne. Votre code peut ressembler à ceci :
io.on("connection", (socket) => { console.log("A user connected"); socket.on("SOME_EVENT", async(param) => { //check whether the column already exists: const column = await db.select... //if column exists, update it: if(column){ await db.update... } //else, create one await db.insert... }) }
Maintenant, le problème est que si les deux clients émettent « SOME_EVENT » simultanément, il y a une chance qu'ils créent tous les deux une nouvelle ligne dans la base de données, ce qui n'est pas ce que nous voulons.
Au lieu de permettre à socketio d'exécuter les fonctions du gestionnaire, nous les intercepterons et déciderons quand elles seront exécutées. Lorsque nous interceptons les gestionnaires, nous les envoyons à notre système de file d’attente, chargé de les mettre en œuvre dans l’ordre.
Le système comportera deux composants principaux : une file d'attente et une boucle d'événements.
En informatique, une file d'attente est une structure de données qui nous permet de stocker et de gérer des données de manière séquentielle. Pour y parvenir, la structure permet uniquement aux données d'être ajoutées à une extrémité (la queue) et de sortir à l'autre extrémité (la tête). Cette caractéristique est communément appelée FIFO, ce qui signifie premier entré, premier sorti.
La file d'attente est un type de données abstrait (ADT). Comme les autres ADT, de nombreux langages de programmation, y compris Javascript, ne l'ont pas par défaut. Dans cet article, nous modéliserons notre file d'attente à l'aide des méthodes unshift et pop du tableau Javascript.
En termes généraux, une boucle d'événements est une construction qui s'exécute à intervalles réguliers et exécute des tâches de manière conditionnelle. Nous utiliserons un setInterval dans notre cas, qui vérifiera constamment si la file d'attente contient des fonctions en attente et appellera la fonction suivante uniquement lorsque la précédente sera terminée.
class QueuingSystem { //We're making the queue private so that it can only be //modified only within the class #queue = []; constructor(interval) { this.interval = interval; } //returns last item of an array: lastItem(arr) { return arr[arr.length - 1]; } //returns true if array is empty: isEmpty(arr) { return arr.length == 0; } //responsible for executing the function at the head of the queue: async run(arr) { //removing the function at the head of the queue: const func = arr.pop(); //adding "false" placeholder at the head to indicate that //a function is being executed: arr.push(false); //executing the function: await func(); //removing the false placeholder at the head to indicate that //the run function is ready to execute the next function: arr.pop(); } //function to add to the tail end of the queue: addToQueue(func) { this.#queue.unshift(func); } //function to start the event loop: start() { return setInterval(() => { //checking if the run method is free by checking if the item at the head is false. //and checking if the array isn't empty if (this.lastItem(this.#queue) !== false && !this.isEmpty(this.#queue)) { this.run(this.#queue); } }, this.interval); } //stopping the event loop if no longer needed: stop() { clearInterval(this.start()); } }
Notre classe de file d'attente de messages est maintenant prête à recevoir et exécuter des fonctions de manière séquentielle.
Avec notre système de file d'attente en place, utilisons-le dans notre code :
//Create a socketQueue that loops after every half of a second: const socketQueue = new QueuingSystem(500) io.on("connection", (socket) => { console.log("A user connected"); const handler = async(param) => { //check whether the column already exists: const column = await db.select... //if column exists, update it: if(column){ await db.update... } //else, create one await db.insert... } socket.on("SOME_EVENT", (param) => { //Add the handler function to socketQueue queue socketQueue.addToQueue(hanlder.bind(null, param)) }) } server.listen(PORT, () => { //start the queuing system: socketQueue.start(); console.log("App listening on port", PORT); });
La méthode bind en Javascript est utilisée pour attacher des fonctions, ainsi que leurs paramètres, à des objets, mais pas pour les appeler. Dans notre cas, nous n’attachons la fonction à aucun objet, c’est pourquoi le premier argument est nul.
La classe de file d'attente de messages que nous avons créée peut nous aider à exécuter séquentiellement des événements avec des gestionnaires asynchrones. Si vous avez besoin d'un système de file d'attente plus complexe, consultez BullMQ ou d'autres solutions robustes. Bon codage !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!