IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

JavaScript Discussion :

Rendre une classe ES6 compatible avec "l’algorithme de clonage structuré" pour passer des données à un worker


Sujet :

JavaScript

  1. #1
    Membre éclairé Avatar de Hervé Saladin
    Homme Profil pro
    Ingénieur d'études en développement et déploiement d'applications
    Inscrit en
    Décembre 2004
    Messages
    647
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur d'études en développement et déploiement d'applications
    Secteur : Service public

    Informations forums :
    Inscription : Décembre 2004
    Messages : 647
    Points : 799
    Points
    799
    Par défaut Rendre une classe ES6 compatible avec "l’algorithme de clonage structuré" pour passer des données à un worker
    Bonjour,
    pour mon appli j'ai écrit une lib composée d'un ensemble de classes ES6 gérant des données (c'est le "modèle" de mon appli), avec de l'héritage, des propriétés, des méthodes, des getters/setters et ces méthodes/propriétés/getters peuvent parfois renvoyer un autre objet lui-même instance d'une classe de la lib.

    Jusqu'ici, tout va bien ...

    Maintenant, certaines fonctionnalités-clés de mon appli peuvent executer des calculs qui sont déjà implémentés et marchent bien, mais qui sont un peu coûteux. Et comme ceux-ci ne font pas appel au DOM et ne modifient pas les données-source, je voudrais les "paralléliser" dans un WebWorker de façon a alléger le thread principal de ma page.

    Et là ça se complique : lors du postMessage au worker, j'ai une erreur :
    Failed to execute 'postMessage' on 'Worker': URL object could not be cloned
    Si j'ai bien compris, c'est parce que JS n'arrive pas à cloner mes objets avec L'algorithme de clonage structuré.
    J'ai essayé de serialiser mon objet avec un JSON.stringify(), mais là j'ai une autre erreur à cause de références cycliques.

    Or, dans le worker, j'ai vraiment besoin des méthodes de mes classes, donc je ne peux pas "réduire" mes objets à des types JS natifs : sans le code de mes classes, le worker n'aurait plus aucun intérêt.

    D'où ma question :

    => Existe-t-il un moyen de personnaliser (de façon "intégrée" proprement au language) la façon dont les instances de mes classes sont clonées (par exemple en codant quelles propriétés sont clonées ou pas et comment ?).

    => A défaut, même question pour leur serialisation avec JSON.parse()

    => Si non, connaissez-vous des contournements qui me permettraient de passer au worker les données ET l'implem des traitements (= les méthodes de mes classes) ?

    Merci d'avance

  2. #2
    Rédacteur

    Avatar de danielhagnoul
    Homme Profil pro
    Étudiant perpétuel
    Inscrit en
    Février 2009
    Messages
    6 389
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 74
    Localisation : Belgique

    Informations professionnelles :
    Activité : Étudiant perpétuel
    Secteur : Enseignement

    Informations forums :
    Inscription : Février 2009
    Messages : 6 389
    Points : 22 937
    Points
    22 937
    Billets dans le blog
    125
    Par défaut


    Je ne connais pas, mais un peu de recherche avec Google me donne deux références :

    1. L’algorithme de clonage structuré
    2. Deep-copying in JavaScript chapitre Structured

  3. #3
    Expert éminent
    Avatar de Watilin
    Homme Profil pro
    En recherche d'emploi
    Inscrit en
    Juin 2010
    Messages
    3 094
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : En recherche d'emploi

    Informations forums :
    Inscription : Juin 2010
    Messages : 3 094
    Points : 6 755
    Points
    6 755
    Par défaut
    La raison pour laquelle il faut cloner les objets pour les passer via postMessage, c’est que le fil principal et le worker ne partagent pas le même contexte JavaScript. Plus bas niveau, ça signifie entre autres que le worker a sa propre zone de mémoire avec un ensemble restreint de fonctionnalités, ce qui lui permet d’être performant.

    postMessage est faite, comme son nom l’indique, pour passer des messages. Ces messages peuvent être des types primitifs (chaînes, nombres, booléens) ou des objets à la structure plus complexe. Dans ce second cas, l’algorithme de clonage structuré est appelé en interne. Comme indiqué dans le lien MDN que tu as toi-même donné, c’est une variante de la sérialisation JSON, un peu plus puissante car elle supporte les graphes cycliques. Mais elle a une limitation importante : elle ne gère pas les références au contexte.

    Dans le cas des workers, vu la séparation des contextes, on comprend facilement qu’il ne peut y avoir de références échangées entre les deux. Mais il y a aussi une conséquence plus subtile : tout ce qui est présent dans la mémoire (au sens physique) du contexte A doit être copié par l’algorithme pour le passer au contexte B. Du coup, il y a un certain nombre de choses qui sont prises en charge au cas par cas par l’algorithme, comme indiqué dans la doc : RegExp, Blob, File, FileList, ImageData au moment où j’écris.
    Et il y a également des choses qui ne sont explicitement pas prises en charge, parmi lesquelles les fonctions et la chaîne de prototypes.

    Ce dernier point est le plus important dans ton cas. Tu le sais peut-être déjà, les classes ES6 sont un enrobage syntaxique (avec quelques ajouts, notamment super) autour du duo constructeur - prototype. En clair avec un exemple, si j’écris cette classe simple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Example {
      constructor(arg) {
        this.prop = arg;
      }
     
      sayHello() {
        console.log("hello");
      }
    }
    Elle est traduite en ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Example(arg) {
      this.prop = arg;
    }
     
    Example.prototype = {
      sayHello: function () {
        console.log("hello");
      }
    };
    Cette traduction n’est pas juste une vue de l’esprit, elle a bel est bien lieu, au plus tard au moment de l’exécution. Dans ta console, si tu tapes typeof Example, tu verras que ça retourne "function". En fait, techniquement, les classes n’existent pas.

    À présent tu vois le problème : tes classes reposent sur les fonctions et les prototypes, or l’algorithme de clonage structuré ne supporte ni les unes ni les autres.
    Tu vas devoir adapter ton code pour que postMessage ne transmette que des données, rien de dynamique. En résumé, tu dois sérialiser toi-même tes objets. Dans le fil principal, tu auras donc une fonction qui adapte les objets sous une forme qui « passe dans le tuyau » ; et dans le worker, tu auras une fonction qui reçoit la forme adaptée et qui reconstruit l’objet correspondant.

  4. #4
    Membre éclairé Avatar de Hervé Saladin
    Homme Profil pro
    Ingénieur d'études en développement et déploiement d'applications
    Inscrit en
    Décembre 2004
    Messages
    647
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur d'études en développement et déploiement d'applications
    Secteur : Service public

    Informations forums :
    Inscription : Décembre 2004
    Messages : 647
    Points : 799
    Points
    799
    Par défaut
    Un très grand merci Watilin, pour cette réponse hyper complète et très claire !

    il y a également des choses qui ne sont explicitement pas prises en charge, parmi lesquelles les fonctions et la chaîne de prototypes.
    D'accord, les fonctions et prototypes ne sont pas passés avec les données dans le message au Worker, mais est-ce que celui-ci peut les retrouver de son propre côté, par exemple en important le module contenant les classes ?

    dans le worker, tu auras une fonction qui reçoit la forme adaptée et qui reconstruit l’objet correspondant
    Mais est-ce que lors de cette reconstruction je peux "retrouver" mes classes d'origine ?

    En clair, dans le Worker est-ce que je peux faire quelque chose équivalent à :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    import {MaClasse} from "./chemin/vers/MaClasse.js";
     
    addEventListener("message", (messageEvent) => {
      const monObjet = (MaClasse)messageEvent.data.monObjet_serialized;
      [...]
    });
    Et si oui, comment ? (je sais bien que le code ci-dessus ne marchera pas en JS, c'est juste pour illustrer ce que je voudrais faire)

  5. #5
    Expert éminent
    Avatar de Watilin
    Homme Profil pro
    En recherche d'emploi
    Inscrit en
    Juin 2010
    Messages
    3 094
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : En recherche d'emploi

    Informations forums :
    Inscription : Juin 2010
    Messages : 3 094
    Points : 6 755
    Points
    6 755
    Par défaut
    Oui, c’est tout à fait possible d’avoir la déclaration de classe dans le code du worker, directement ou importée (de préférence importée pour éviter la duplication de code).

    Comme il n’y a pas de syntaxe typecast en JS, tu dois reconstruire tes objets avec new MaClasse() en utilisant les données issues de la forme sérialisée. Bien sûr, cela suppose que ton constructeur est déterministe, autrement dit que deux objets construits de la même façon vont se comporter de la même façon.

    Je ne peux donner qu’un exemple très simpliste car je ne connais pas les détails de ta classe, mais voilà.
    La déclaration de classe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class MaClasse {
      constructor(reponse) {
        this.reponse = reponse;
      }
    }
    Le worker :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    import {MaClasse} from "./chemin/vers/MaClasse.js";
     
    addEventListener("message", (messageEvent) => {
      const serialized = messageEvent.data.monObjet_serialized;
      const monObjet = new MaClasse(serialized.reponse);
      [...]
    });
    Le fil principal :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    [...]
    worker.postMessage({ reponse: 42 });
    [...]

Discussions similaires

  1. Rendre une classe compatible avec tous les navigateurs
    Par BXDSPORT dans le forum Mise en page CSS
    Réponses: 3
    Dernier message: 20/02/2014, 12h06
  2. Réponses: 5
    Dernier message: 21/10/2007, 14h33
  3. Réponses: 1
    Dernier message: 05/06/2007, 13h04
  4. rendre une classe serializable sans les codes sources
    Par LittleBean dans le forum Langage
    Réponses: 2
    Dernier message: 19/04/2007, 11h47
  5. Réponses: 13
    Dernier message: 02/02/2005, 01h21

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo