Partage
  • Partager sur Facebook
  • Partager sur Twitter

Utilisation de Promise.all()

Sujet résolu
    22 octobre 2021 à 19:18:07

    Bien le bonjour.

    Voici mon souci, sur mon projet de site, un utilisateur a la possibilité, en cliquant sur le nom d'un autre utilisateur connecté, de lui envoyer une invitation. Ceci va conduire à l'ajout, dans la variable de session de chaque utilisateur, d'une variable "hostInvite" pour le premier, et "guestInvite" pour le second. Et une fois que ces données sont ajoutées dans les sessions ( stockées dans une BDD mongo ), le serveur envoie un message à chacun, message qui est censé contenir les nouvelles données de sessions.
    Problème, l'ajout de ces données dans la BDD demande un temps de traitement ( logique ) et si j'envoie le message directement après avoir appelé les fonctions qui ajoutent ces données, les données de sessions ne sont pas encore mises à jour, joies de l'asynchrone.

    Alors bon, n'étant pas encore super à l'aise avec les Promises, j'ai géré ça de manière "pas beau caca" avec un vilain setTimeout.
    Je voudrais donc faire plus propre avec un Promise.all(), mais je ne sais pas bien comment mettre ça en place.
    Voici du code qui rendra les choses plus claires :

    socket.on("invite", (data) => {
            io.to(data.guest).emit('message', data.host + " vous a invité!");
            
            ////
            //TODO : mieux gérer l'asynchrone, émettre les messages une fois que la BDD est mise à jour
            // https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
            ////
            sessionsManage.findUserSessionId(_dbConnexion, data.host).then((sessionId) => {
                sessionsManage.addHostInvite(_dbConnexion, sessionId, data.guest);
            });
            sessionsManage.findUserSessionId(_dbConnexion, data.guest).then((sessionId) => {
                sessionsManage.addGuestInvite(_dbConnexion, sessionId, data.host);
            });
            
            setTimeout(function(){
                sessionsManage.findSessions(_dbConnexion).then((value) => {
                    //socket.broadcast.emit('newConnection', {newOne: socket.username, userList: value});
                    io.to(data.host).emit('updateUserList', {userList: value});
                    io.to(data.guest).emit('updateUserList', {userList: value});
            })}, 2000);
            ////
            // Fin TODO
            ////
            
        });

    Et voici les deux fonctions que je vais probablement devoir transformer en Promises :

    var addHostInvite = function(dbConnexion, docId, guest){
        var userDoc = dbConnexion.db('mydb').collection('sessions').find({_id: docId});
        userDoc.forEach((doc) => {
            var hostSessionObject = JSON.parse(doc.session);
            if(!hostSessionObject.hostInvites)
                hostSessionObject.hostInvites = [guest];
            else
            {
                if(hostSessionObject.hostInvites.indexOf(guest)>-1)
                    console.log('invitation déjà envoyée!');
                else
                    hostSessionObject.hostInvites.push(guest);
            }
            var hostSessionString = JSON.stringify(hostSessionObject);
            dbConnexion.db('mydb').collection('sessions').updateOne({_id: docId}, { $set: {session: hostSessionString}});
        },
        () => {console.log('ajout de l\'hostInvite terminé');}
        );
    }
    
    var addGuestInvite = function(dbConnexion, docId, host){
        var userDoc = dbConnexion.db('mydb').collection('sessions').find({_id: docId});
        userDoc.forEach((doc) => {
            var guestSessionObject = JSON.parse(doc.session);
            if(!guestSessionObject.guestInvites)
                guestSessionObject.guestInvites = [host];
            else
            {
                if(guestSessionObject.guestInvites.indexOf(guest)>-1)
                    console.log('invitation déjà reçue!');
                else
                    guestSessionObject.guestInvites.push(host);
            }
            var guestSessionString = JSON.stringify(guestSessionObject);
            dbConnexion.db('mydb').collection('sessions').updateOne({_id: docId}, { $set: {session: guestSessionString}});
        },
        () => {console.log('ajout de la guestInvite terminé');}
        );
    }

    Alors voilà, j'ai bien comme idée de retourner quelque chose dans les callbacks des forEach(), ou en troisième argument de la fonction updateOne?

    Quelle serait la manière la plus "propre" de procéder pour utiliser Promise.all() plutôt que setTimeout() ?

    D'avance merci


    • Partager sur Facebook
    • Partager sur Twitter
      22 octobre 2021 à 21:21:08

      Salut,

      Tiens, le secret des promesses : https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Promise

      On fait en sorte que tes fonctions renvois une promesse chacune, qui est résolue lorsque la logique est terminée :

      var addHostInvite = function(dbConnexion, docId, guest){
          return new Promise(res => { // <== Ici on renvoi une nouvelle promesse et lance le reste de la routine.
              var userDoc = dbConnexion.db('mydb').collection('sessions').find({_id: docId});
              userDoc.forEach((doc) => {
                  var hostSessionObject = JSON.parse(doc.session);
                  if(!hostSessionObject.hostInvites)
                      hostSessionObject.hostInvites = [guest];
                  else
                  {
                      if(hostSessionObject.hostInvites.indexOf(guest)>-1)
                          console.log('invitation déjà envoyée!');
                      else
                          hostSessionObject.hostInvites.push(guest);
                  }
                  var hostSessionString = JSON.stringify(hostSessionObject);
                  dbConnexion.db('mydb').collection('sessions').updateOne({_id: docId}, { $set: {session: hostSessionString}});
              },
              () => {
                  console.log('ajout de l\'hostInvite terminé');
                  res(); // <== Tout est terminé, on résout la promesse grâce à la fonction de résolution qu'on a appelé "res"
              });
          });
      };
       
      var addGuestInvite = function(dbConnexion, docId, host){
          return new Promise(res => { // <== idem
              var userDoc = dbConnexion.db('mydb').collection('sessions').find({_id: docId});
              userDoc.forEach((doc) => {
                  var guestSessionObject = JSON.parse(doc.session);
                  if(!guestSessionObject.guestInvites)
                      guestSessionObject.guestInvites = [host];
                  else
                  {
                      if(guestSessionObject.guestInvites.indexOf(guest)>-1)
                          console.log('invitation déjà reçue!');
                      else
                          guestSessionObject.guestInvites.push(host);
                  }
                  var guestSessionString = JSON.stringify(guestSessionObject);
                  dbConnexion.db('mydb').collection('sessions').updateOne({_id: docId}, { $set: {session: guestSessionString}});
              },
              () => {
                  console.log('ajout de la guestInvite terminé');
                  res(); // <== idem
              });
          });
      };

      Maintenant, tu peux attendre des promesses : 

      socket.on("invite", async (data) => { // <== On se place en fonction asynchrone pour pouvoir attendre les promesses.
          io.to(data.guest).emit('message', data.host + " vous a invité!");
      
          const [
              hostSessionId,
              guestSessionId
          ] = await Promise.all([
              sessionsManage.findUserSessionId(_dbConnexion, data.host),
              sessionsManage.findUserSessionId(_dbConnexion, data.guest)
          ]); // <== Un premier await Promise.all pour attendre le moment ou tu à reçu les résultats de tes deux requêtes.
      
          await Promise.all([
              sessionsManage.addHostInvite(_dbConnexion, hostSessionId, data.guest),
              sessionsManage.addGuestInvite(_dbConnexion, guestSessionId, data.host)
          ]); // <== Un second await Promise.all pour attendre la résolution des promesses de tes deux fonctions nouvellement modifiées de sorte à ce qu'elles renvoies ces dites promesses.
      
          // Ton setTimeout un peu douteux (il faut le dire) ne sert plus a rien, tes requêtes sont terminées lorsque la suite du code est exécuté.
      
          const userList = await sessionsManage.findSessions(_dbConnexion); // On attend le résultat de cette requête, et on poursuit la routine.
          io.to(data.host).emit('updateUserList', {userList});
          io.to(data.guest).emit('updateUserList', {userList});
      });
      

      -
      Edité par BrainError 22 octobre 2021 à 21:26:10

      • Partager sur Facebook
      • Partager sur Twitter
        22 octobre 2021 à 21:53:34

        D'accord, je n'aurais jamais pensé à formuler de cette manière, même si j'avais un début de bonne idée.
        Je suis encore un peu dérouté par ce genre de syntaxe, aussi :

        const [hostSessionId, guestSessionId] = await Promise.all([fonction1(), fonction2()]);

        au lieu d'un simple nom de variable, on déclare un array de nom de variable ?
        Si il y a de la doc là-dessus, je suis preneur aussi ^^

        Bon, sinon j'avais déjà un peu lu la doc MDN sur les promises, plutôt compris l'idée générale, mais encore un peu timide à mettre en pratique ^^
        En tout cas un très grand merci à toi pour ce code bien commenté, ça m'aide beaucoup, en tout cas je comprend ce que je lis, et ça c'est bon pour mon moral :)

        Bon, là il est tard, je met ça en place demain et je vois si j'y arrive avant de mettre mon sujet comme résolu.

        [edit]

        Bon, j'ai modifié mon code, j'ai essayé de réécrire moi-même plutôt que de bêtement copier/coller, et j'ai beau lire et relire, je ne vois pas où je me suis planté.
        Mais j'ai une erreur au lancement de l'app :

        const [hostSessionId, guestSessionId] = await Promise.all([
        
        SyntaxError: await is only valid in async function

        Bon, seule petite modif par rapport à ta proposition, dans les fonctions addHostInvite et addGuestInvite, j'ai mis "resolve" plutôt que "res", parce que c'est une application Express, et qu'il y a déjà les "req, res" ( pour request et response ), mais je ne vois pas en quoi ça serait la source d'erreur, c'est une formulation plutôt classique dont je me suis déjà servi par ailleurs.

        Alors bon, j'ai déjà essayé deux ou trois fois d'appliquer des exemples avec "await", et j'ai toujours eu cette erreur. J'ai tout naturellement pensé que c'était moi qui me plantait, mais du coup, est-il possible que ça vienne, je sais pas, d'une mauvaise version de node, ou d'un node_module ? ( et là je ne sais vraiment pas où chercher, ou quoi poster pour qu'on m'aide à résoudre cet hypothétique problème )


        -
        Edité par LucasWerquin 23 octobre 2021 à 8:58:53

        • Partager sur Facebook
        • Partager sur Twitter
          23 octobre 2021 à 9:12:16

          Salut,

          L'erreur parle d'elle même, tu as oublié de te placer dans une fonction asynchrone quelque part.

          En effet, impossible d'utiliser await dans une fonction synchrone classique.

          Si tu veux pouvoir attendre une promesse, il faut que tu le face depuis une fonction qui renverra elle-même une promesse.

          C'est la seule "contrainte" (mais s'en est pas vraiment une en vérité) à l'écriture de code asynchrone de cette façon : tu peux pas tout à fait mélanger salades et tomates sur ton étal. Tout ce qui est asynchrone doit rester asynchrone, en revanche ce qui est synchrone reste accessible dans l'asynchrone.

          Le second morceau de code que je partage, à la ligne 1 : je redéfini ta fonction flèchée en fonction flèchée asynchrone avec "async". C'est là ta solution.

          -
          Edité par BrainError 23 octobre 2021 à 9:13:42

          • Partager sur Facebook
          • Partager sur Twitter
            23 octobre 2021 à 9:30:23

            Oh mais quel bécasse je fais !

            C'est effectivement le petit truc que j'ai oublié de modifier ^^

            Bon, en attendant, je me suis quand même retroussé les manches et j'ai trouvé une solution sans async / await, que avec des .then() :

            socket.on("invite", (data) => {
                    io.to(data.guest).emit('message', data.host + " vous a invité!");
                    
                    Promise.all([
                        sessionsManage.findUserSessionId(_dbConnexion, data.host),
                        sessionsManage.findUserSessionId(_dbConnexion, data.guest)
                    ]).then((values) => {
                        Promise.all([
                            sessionsManage.addHostInvite(_dbConnexion, values[0], data.guest),
                            sessionsManage.addGuestInvite(_dbConnexion, values[1], data.host)
                        ]).then(() =>{
                            sessionsManage.findSessions(_dbConnexion).then((value) => {
                                io.to(data.host).emit('updateUserList', {userList: value});
                                io.to(data.guest).emit('updateUserList', {userList: value});
                            });
                        })
                    });
                });

            Bon, c'est moins joli, mais au moins ça me rassure un peu : j'ai quand même acquis quelques notions d'asynchrone ;)
            Du coup je garde ça dans un coin, et je réessaye ta proposition.

            [edit]

            Ben oui, le petit mot qui change tout.
            Donc, pour résumer:
            - ta proposition fonctionne nickel chrome ( et firefox aussi ^^ )
            - j'avais quand même plus ou moins compris les grandes lignes de l'asynchrone, mais je manque de pratique
            - j'ai ENFIN réussi à placer un async / await !
            - le coup du setTimeout() c'est caca ( mais je le savais déjà avant même de l'écrire ) parce que le processus prend quelques millisecondes, et pas deux secondes. L'invitation apparaît quasi instantanément chez les clients et ça c'est cool.
            - Tu m'as fait faire de gros progrès en très peu de temps, même sur cette histoire de déclarer un array de variables ( const [var1, var2] ) où je commence à avoir un début de compréhension ( mais il faut que je trouve une source sur ce genre de syntaxe que je ne connais pas ).

            Sujet résolu :)

            -
            Edité par LucasWerquin 23 octobre 2021 à 10:00:14

            • Partager sur Facebook
            • Partager sur Twitter

            Utilisation de Promise.all()

            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
            • Editeur
            • Markdown