Motif singleton dans nodejs - est-ce nécessaire?


95

Je suis récemment tombé sur cet article sur la façon d'écrire un singleton dans Node.js. Je connais la documentation des require états que:

Les modules sont mis en cache après leur premier chargement. Plusieurs appels à require('foo')peuvent ne pas entraîner l'exécution du code du module plusieurs fois.

Il semble donc que chaque module requis puisse être facilement utilisé comme un singleton sans le code standard du singleton.

Question:

L'article ci-dessus fournit-il une solution complète pour créer un singleton?


1
Voici un 5 min. explication sur ce sujet (écrite après la v6 et npm3): medium.com/@lazlojuly/…
lazlojuly

Réponses:


56

Cela a essentiellement à voir avec la mise en cache de nodejs. Clair et simple.

https://nodejs.org/api/modules.html#modules_caching

(v 6.3.1)

Mise en cache

Les modules sont mis en cache après leur premier chargement. Cela signifie (entre autres) que chaque appel à require ('foo') obtiendra exactement le même objet renvoyé, s'il se résoudrait dans le même fichier.

Plusieurs appels à require ('foo') peuvent ne pas provoquer l'exécution du code du module plusieurs fois. C'est une caractéristique importante. Avec lui, des objets "partiellement terminés" peuvent être retournés, permettant ainsi aux dépendances transitives d'être chargées même lorsqu'elles provoqueraient des cycles.

Si vous souhaitez qu'un module exécute du code plusieurs fois, exportez une fonction et appelez cette fonction.

Mises en garde relatives à la mise en cache du module

Les modules sont mis en cache en fonction de leur nom de fichier résolu. Étant donné que les modules peuvent se résoudre en un nom de fichier différent en fonction de l'emplacement du module appelant (chargement à partir des dossiers node_modules), ce n'est pas une garantie que require ('foo') renverra toujours exactement le même objet, s'il se résoudrait à des fichiers différents .

De plus, sur les systèmes de fichiers ou les systèmes d'exploitation insensibles à la casse, différents noms de fichiers résolus peuvent pointer vers le même fichier, mais le cache les traitera toujours comme des modules différents et rechargera le fichier plusieurs fois. Par exemple, require ('./ foo') et require ('./ FOO') renvoient deux objets différents, que ./foo et ./FOO soient ou non le même fichier.

Donc en termes simples.

Si vous voulez un Singleton; exporter un objet .

Si vous ne voulez pas de Singleton; exporter une fonction (et faire des trucs / renvoyer des trucs / quoi que ce soit dans cette fonction).

Pour être TRÈS clair, si vous faites cela correctement, cela devrait fonctionner, regardez https://stackoverflow.com/a/33746703/1137669 (réponse d'Allen Luce). Il explique dans le code ce qui se passe lorsque la mise en cache échoue en raison de noms de fichiers résolus différemment. Mais si vous résolvez TOUJOURS le même nom de fichier, cela devrait fonctionner.

Mise à jour 2016

création d'un vrai singleton dans node.js avec des symboles es6 Une autre solution : dans ce lien

Mise à jour 2020

Cette réponse se réfère à CommonJS (la méthode propre à Node.js pour importer / exporter des modules). Node.js basculera probablement vers les modules ECMAScript : https://nodejs.org/api/esm.html (ECMAScript est le vrai nom de JavaScript si vous ne le saviez pas)

Lors de la migration vers ECMAScript, lisez ce qui suit pour le moment: https://nodejs.org/api/esm.html#esm_writing_dual_packages_ While_avoiding_or_minimizing_hazards


3
Si vous voulez un Singleton; exporter un objet ... qui a aidé merci
danday74

1
C'est une sorte de mauvaise idée - pour de nombreuses raisons données ailleurs sur cette page - mais le concept est essentiellement valable, c'est-à-dire que dans les circonstances nominales établies, les affirmations de cette réponse sont vraies. Si vous voulez un singleton rapide et sale, cela fonctionnera probablement - ne lancez simplement aucune navette avec le code.
Adam Tolley

@AdamTolley "pour de nombreuses raisons données ailleurs sur cette page", faites-vous référence à des fichiers symétriques ou à des noms de fichiers mal orthographiés qui apparemment n'utilisent pas le même cache? Il indique dans la documentation le problème concernant les systèmes de fichiers ou les systèmes d'exploitation insensibles à la casse. En ce qui concerne les liens symboliques, vous pouvez en savoir plus ici car cela a été discuté sur github.com/nodejs/node/issues/3402 . De plus, si vous créez des liens symboliques ou que vous ne comprenez pas correctement votre système d'exploitation et votre nœud, vous ne devriez pas être à proximité de l'industrie de l'ingénierie aérospatiale;), je comprends cependant votre point ^^.
K - La toxicité du SO augmente.

@KarlMorrison - juste pour le fait que la documentation ne le garantit pas, le fait qu'il semble être un comportement non spécifié, ou toute autre raison rationnelle de ne pas faire confiance à ce comportement particulier de la langue. Peut-être que le cache fonctionne différemment dans une autre implémentation, ou vous aimez travailler dans les REPL et subvertir complètement la fonction de mise en cache. Mon point est que le cache est un détail d'implémentation, et son utilisation comme équivalent singleton est un hack intelligent. J'adore les hacks intelligents, mais ils devraient être différenciés, c'est tout - (personne ne lance de navettes avec nœud,
Adam Tolley

136

Tout ce qui précède est trop compliqué. Il y a une école de pensée qui dit que les modèles de conception montrent des lacunes du langage réel.

Les langages avec une POO basée sur un prototype (sans classe) n'ont pas du tout besoin d'un modèle de singleton. Vous créez simplement un seul objet (tonne) à la volée, puis vous l'utilisez.

En ce qui concerne les modules dans node, oui, par défaut, ils sont mis en cache, mais cela peut être modifié par exemple si vous voulez un chargement à chaud des modifications de modules.

Mais oui, si vous souhaitez utiliser un objet partagé partout, le mettre dans un module d'export est très bien. Il suffit de ne pas le compliquer avec un "motif singleton", pas besoin de cela en JavaScript.


27
C'est bizarre que personne n'obtienne des votes positifs ... avoir un +1 pourThere is a school of thought which says design patterns are showing deficiencies of actual language.
Esailija

64
Les singletons ne sont pas un anti-pattern.
wprl

4
@herby, semble être une définition trop spécifique (et donc incorrecte) du modèle de singleton.
wprl

19
La documentation lit: "Plusieurs appels à require ('foo') ne peuvent pas provoquer l'exécution du code du module plusieurs fois.". Il dit "peut-être pas", il ne dit pas "ne sera pas", donc demander comment s'assurer que l'instance de module n'est créée qu'une seule fois dans une application est une question valable de mon point de vue.
xorcus

9
Il est trompeur que ce soit la bonne réponse à cette question. Comme @mike l'a souligné ci-dessous, il est possible qu'un module soit chargé plus d'une fois et que vous ayez deux instances. Je rencontre ce problème où je n'ai qu'une copie de Knockout, mais deux instances sont créées car le module est chargé deux fois.
dgaviola

26

Non. Lorsque la mise en cache du module de Node échoue, ce modèle de singleton échoue. J'ai modifié l'exemple pour fonctionner de manière significative sur OSX:

var sg = require("./singleton.js");
var sg2 = require("./singleton.js");
sg.add(1, "test");
sg2.add(2, "test2");

console.log(sg.getSocketList(), sg2.getSocketList());

Cela donne le résultat attendu par l'auteur:

{ '1': 'test', '2': 'test2' } { '1': 'test', '2': 'test2' }

Mais une petite modification empêche la mise en cache. Sous OSX, procédez comme suit:

var sg = require("./singleton.js");
var sg2 = require("./SINGLETON.js");
sg.add(1, "test");
sg2.add(2, "test2");

console.log(sg.getSocketList(), sg2.getSocketList());

Ou, sous Linux:

% ln singleton.js singleton2.js

Puis changez la sg2ligne requise en:

var sg2 = require("./singleton2.js");

Et bam , le singleton est vaincu:

{ '1': 'test' } { '2': 'test2' }

Je ne connais pas de moyen acceptable de contourner cela. Si vous vous sentez vraiment la nécessité de faire quelque chose comme singleton et sont d' accord avec la pollution de l'espace de nommage global (et les nombreux problèmes qui peuvent en résulter), vous pouvez changer de l'auteur getInstance()etexports lignes en:

singleton.getInstance = function(){
  if(global.singleton_instance === undefined)
    global.singleton_instance = new singleton();
  return global.singleton_instance;
}

module.exports = singleton.getInstance();

Cela dit, je n'ai jamais rencontré de situation sur un système de production où je devais faire quelque chose comme ça. Je n'ai jamais ressenti le besoin d'utiliser le modèle singleton en Javascript.


21

En regardant un peu plus loin les avertissements de mise en cache des modules dans la documentation des modules:

Les modules sont mis en cache en fonction de leur nom de fichier résolu. Étant donné que les modules peuvent se résoudre en un nom de fichier différent en fonction de l'emplacement du module appelant (chargement à partir des dossiers node_modules), ce n'est pas une garantie que require ('foo') renverra toujours exactement le même objet, s'il se résoudrait à des fichiers différents .

Ainsi, selon l'endroit où vous vous trouvez lorsque vous avez besoin d'un module, il est possible d'obtenir une instance différente du module.

On dirait que les modules ne sont pas une solution simple pour créer des singletons.

Edit: Ou peut-être qu'ils le sont . Comme @mkoryak, je ne peux pas proposer de cas où un seul fichier pourrait se résoudre en différents noms de fichiers (sans utiliser de liens symboliques). Mais (comme le commente @JohnnyHK), plusieurs copies d'un fichier dans différents node_modulesrépertoires seront chacune chargées et stockées séparément.


ok, j'ai lu que 3 fois et je ne peux toujours pas penser à un exemple où il résoudrait en un nom de fichier différent. Aidez-moi?
mkoryak

1
@mkoryak Je pense que cela fait référence aux cas où vous avez besoin de deux modules différents node_modulesdont chacun dépend du même module, mais il y a des copies séparées de ce module dépendant dans le sous- node_modulesrépertoire de chacun des deux modules différents.
JohnnyHK

@mike vous êtes ici, ce module est instancié plusieurs fois lorsqu'il est référencé via différents chemins. J'ai frappé le cas lors de l'écriture de tests unitaires pour les modules serveur. J'ai besoin d'une sorte d'instance singleton. comment y parvenir?
Sushil

Un exemple pourrait être des chemins relatifs. par exemple. Étant donné qu'il require('./db')est dans deux fichiers séparés, le code du dbmodule s'exécute deux fois
écrit

9
Je viens d'avoir un bogue désagréable car le système de module de nœud est insensible à la casse. J'ai appelé require('../lib/myModule.js');dans un fichier et require('../lib/mymodule.js');dans un autre et il n'a pas livré le même objet.
heyarne

18

Un singleton dans node.js (ou dans le navigateur JS, d'ailleurs) comme ça est complètement inutile.

Puisque les modules sont mis en cache et avec état, l'exemple donné sur le lien que vous avez fourni pourrait facilement être réécrit beaucoup plus simplement:

var socketList = {};

exports.add = function (userId, socket) {
    if (!socketList[userId]) {
        socketList[userId] = socket;
    }
};

exports.remove = function (userId) {
    delete socketList[userId];
};

exports.getSocketList = function () {
    return socketList;
};
// or
// exports.socketList = socketList

5
Les documents disent " peut ne pas provoquer l'exécution du code du module plusieurs fois", il est donc possible qu'il soit appelé plusieurs fois, et si ce code est exécuté à nouveau, la socketList sera réinitialisée à une liste vide
Jonathan.

3
@Jonathan. Le contexte dans la documentation autour de cette citation semble faire un cas assez convaincant qui n'est peut -être pas utilisé dans un style RFC NE DOIT PAS .
Michael

6
@Michael "peut" est un mot drôle comme ça. Envie d'avoir un mot qui, lorsqu'il est nié, signifie "peut-être pas" ou "certainement pas" ..
OJFord

1
Le may nots'applique lorsque vous avez d' npm linkautres modules pendant le développement. Soyez donc prudent lorsque vous utilisez des modules qui reposent sur une seule instance telle qu'un eventBus.
mediafreakch

10

La seule réponse ici qui utilise les classes ES6

// SummaryModule.js
class Summary {

  init(summary) {
    this.summary = summary
  }

  anotherMethod() {
    // do something
  }
}

module.exports = new Summary()

nécessitent ce singleton avec:

const summary = require('./SummaryModule')
summary.init(true)
summary.anotherMethod()

Le seul problème ici est que vous ne pouvez pas passer de paramètres au constructeur de classe, mais cela peut être contourné en appelant manuellement une initméthode.


la question est "faut-il des singletons", pas "comment en écrire un"
mkoryak

@ danday74 Comment pouvons-nous utiliser la même instance summarydans une autre classe, sans la réinitialiser?
M Faisal Hameed

1
Dans Node.js, il suffit de l'exiger dans un autre fichier ... const summary = require ('./ SummaryModule') ... et ce sera la même instance. Vous pouvez tester cela en créant une variable membre et en définissant sa valeur dans un fichier qui en a besoin, puis en obtenant sa valeur dans un autre fichier qui en a besoin. Ce devrait être la valeur qui a été définie.
danday74

9

Vous n'avez besoin de rien de spécial pour faire un singleton en js, le code de l'article pourrait tout aussi bien être:

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

En dehors de node.js (par exemple, dans le navigateur js), vous devez ajouter la fonction wrapper manuellement (cela se fait automatiquement dans node.js):

var singleton = function() {
    var socketList = {};
    return {
        add: function() {},
        ...
    };
}();

Comme l'a souligné @Allen Luce, si la mise en cache du nœud échoue, le modèle de singleton échoue également.
rakeen

6

Les singletons sont bien dans JS, ils n'ont tout simplement pas besoin d'être aussi verbeux.

Dans node si vous avez besoin d'un singleton, par exemple pour utiliser la même instance ORM / DB dans divers fichiers de votre couche serveur, vous pouvez insérer la référence dans une variable globale.

Écrivez simplement un module qui crée la variable globale si elle n'existe pas, puis renvoie une référence à cela.

@ allen-luce avait raison avec son exemple de code de note de bas de page copié ici:

singleton.getInstance = function(){
  if(global.singleton_instance === undefined)
    global.singleton_instance = new singleton();
  return global.singleton_instance;
};

module.exports = singleton.getInstance();

mais il est important de noter que l'utilisation du newmot-clé n'est pas obligatoire. Tout ancien objet, fonction, iife, etc. fonctionnera - il n'y a pas de vaudou POO qui se passe ici.

des points bonus si vous fermez un objet dans une fonction qui lui renvoie une référence, et que vous en faites une fonction globale - alors même la réaffectation de la variable globale ne supprimera pas les instances déjà créées à partir de celle-ci - bien que cela soit très utile.


vous n'avez besoin de rien de tout cela. vous pouvez simplement le faire module.exports = new Foo()parce que module.exports ne s'exécutera pas à nouveau, à moins que vous ne fassiez quelque chose de vraiment stupide
mkoryak

Vous ne devez absolument PAS vous fier aux effets secondaires de la mise en œuvre. Si vous avez besoin d'une seule instance, liez-la simplement à une instance globale, au cas où l'implémentation changerait.
Adam Tolley

La réponse ci-dessus était également un malentendu de la question originale comme «Dois-je utiliser des singletons dans JS ou est-ce que le langage les rend inutiles?», Ce qui semble également être un problème avec beaucoup d'autres réponses. Je maintiens ma recommandation contre l'utilisation de l'implémentation require en remplacement d'une implémentation singleton correcte et explicite.
Adam Tolley

1

Rester simple.

toto.js

function foo() {

  bar: {
    doSomething: function(arg, callback) {
      return callback('Echo ' + arg);
    };
  }

  return bar;
};

module.exports = foo();

Alors juste

var foo = require(__dirname + 'foo');
foo.doSomething('Hello', function(result){ console.log(result); });

la question est "faut-il des singletons", pas "comment en écrire un"
mkoryak
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.