Pola tunggal di nodejs - apakah diperlukan?

95

Saya baru-baru ini menemukan artikel ini tentang cara menulis singleton di Node.js. Saya tahu dokumentasi require menyatakan bahwa:

Modul di-cache setelah pertama kali dimuat. Beberapa panggilan ke require('foo')mungkin tidak menyebabkan kode modul dijalankan berkali-kali.

Jadi tampaknya setiap modul yang diperlukan dapat dengan mudah digunakan sebagai singleton tanpa kode boilerplate tunggal.

Pertanyaan:

Apakah artikel di atas memberikan penjelasan tentang solusi untuk membuat singleton?

mkoryak
sumber
1
Ini adalah 5 menit. penjelasan tentang topik ini (ditulis setelah v6 dan npm3): medium.com/@lazlojuly/…
lazlojuly

Jawaban:

56

Ini pada dasarnya berkaitan dengan cache nodejs. Polos dan sederhana.

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

(v 6.3.1)

Caching

Modul di-cache setelah pertama kali dimuat. Ini berarti (antara lain) bahwa setiap panggilan ke require ('foo') akan mendapatkan objek yang persis sama yang dikembalikan, jika itu akan menyelesaikan ke file yang sama.

Beberapa panggilan yang memerlukan ('foo') mungkin tidak menyebabkan kode modul dijalankan beberapa kali. Ini adalah fitur penting. Dengan itu, objek "sebagian selesai" dapat dikembalikan, sehingga memungkinkan dependensi transitif dimuat bahkan ketika mereka akan menyebabkan siklus.

Jika Anda ingin modul mengeksekusi kode beberapa kali, maka ekspor fungsi, dan panggil fungsi itu.

Modul Caching Peringatan

Modul di-cache berdasarkan nama file yang diselesaikan. Karena modul dapat menyelesaikan ke nama file yang berbeda berdasarkan lokasi modul pemanggil (memuat dari folder node_modules), itu bukan jaminan bahwa require ('foo') akan selalu mengembalikan objek yang sama persis, jika itu akan menyelesaikan ke file yang berbeda .

Selain itu, pada sistem file atau sistem operasi yang tidak membedakan huruf besar / kecil, nama file yang diselesaikan berbeda dapat mengarah ke file yang sama, tetapi cache akan tetap memperlakukannya sebagai modul yang berbeda dan akan memuat ulang file beberapa kali. Misalnya, require ('./ foo') dan require ('./ FOO') menampilkan dua objek berbeda, terlepas dari apakah ./foo dan ./FOO adalah file yang sama atau tidak.

Jadi secara sederhana.

Jika Anda menginginkan Singleton; ekspor sebuah objek .

Jika Anda tidak menginginkan Singleton; ekspor fungsi (dan lakukan barang / kembalikan barang / apa pun di fungsi itu).

Agar SANGAT jelas, jika Anda melakukan ini dengan benar, seharusnya berfungsi, lihat https://stackoverflow.com/a/33746703/1137669 (jawaban Allen Luce). Ini menjelaskan dalam kode apa yang terjadi ketika caching gagal karena nama file yang diselesaikan secara berbeda. Tetapi jika Anda SELALU menyelesaikan dengan nama file yang sama, seharusnya berfungsi.

Perbarui 2016

membuat singleton yang sebenarnya di node.js dengan simbol es6 Solusi lain : di tautan ini

Perbarui 2020

Jawaban ini mengacu pada CommonJS (cara Node.js sendiri untuk mengimpor / mengekspor modul). Node.js kemungkinan besar akan beralih ke Modul ECMAScript : https://nodejs.org/api/esm.html (ECMAScript adalah nama asli JavaScript jika Anda tidak tahu)

Saat bermigrasi ke ECMAScript, baca berikut ini untuk saat ini: https://nodejs.org/api/esm.html#esm_writing_dual_packages_ sementara_avoiding_or_minimizing_hazards

basickarl.dll
sumber
3
Jika Anda menginginkan Singleton; ekspor objek ... yang membantu terima kasih
danday74
1
Ini semacam ide yang buruk - karena banyak alasan yang diberikan di tempat lain di halaman ini - tetapi konsep tersebut pada dasarnya valid, yaitu dalam keadaan nominal yang ditetapkan, klaim dalam jawaban ini benar. Jika Anda menginginkan singleton yang cepat dan kotor, ini kemungkinan akan berhasil - jangan meluncurkan angkutan apa pun dengan kode.
Adam Tolley
@AdamTolley "karena berbagai alasan yang diberikan di tempat lain di halaman ini", apakah Anda mengacu pada file symlink atau nama file yang salah eja yang tampaknya tidak menggunakan cache yang sama? Itu menyatakan dalam dokumentasi masalah mengenai sistem file atau sistem operasi peka huruf besar kecil. Mengenai symlinking, Anda dapat membaca lebih lanjut di sini seperti yang telah dibahas github.com/nodejs/node/issues/3402 . Juga jika Anda menautkan file atau tidak memahami OS dan node Anda dengan benar maka Anda tidak boleh berada di dekat industri teknik dirgantara;), saya memahami maksud Anda ^^.
basickarl
@KarlMorrison - hanya karena dokumentasi tidak menjaminnya, fakta bahwa ini tampaknya merupakan perilaku yang tidak ditentukan, atau alasan rasional lainnya untuk tidak mempercayai perilaku bahasa tertentu ini. Mungkin cache bekerja secara berbeda dalam implementasi lain, atau Anda suka bekerja di REPL dan menumbangkan fitur caching sama sekali. Maksud saya adalah cache adalah detail implementasi, dan penggunaannya sebagai padanan tunggal adalah peretasan yang cerdas. Saya suka peretasan yang cerdas, tetapi itu harus dibedakan, itu saja - (juga tidak ada yang meluncurkan angkutan dengan node, saya bersikap konyol)
Adam Tolley
136

Semua hal di atas terlalu rumit. Ada aliran pemikiran yang mengatakan pola desain menunjukkan kekurangan bahasa yang sebenarnya.

Bahasa dengan OOP berbasis prototipe (tanpa kelas) sama sekali tidak membutuhkan pola tunggal. Anda cukup membuat satu (ton) objek dengan cepat dan kemudian menggunakannya.

Adapun modul dalam node, ya, secara default mereka di-cache, tetapi dapat di-tweak misalnya jika Anda ingin hot-loading perubahan modul.

Tapi ya, jika Anda ingin menggunakan objek bersama secara keseluruhan, memasukkannya ke dalam modul ekspor tidak masalah. Hanya saja, jangan memperumitnya dengan "pola tunggal", tidak perlu di JavaScript.


sumber
27
Aneh sekali tidak ada yang mendapatkan suara positif ... miliki +1 untukThere is a school of thought which says design patterns are showing deficiencies of actual language.
Esailija
64
Lajang bukanlah anti-pola.
wprl
5
@herby, sepertinya definisi pola singleton yang terlalu spesifik (dan karena itu salah).
wprl
20
Dokumentasinya berbunyi: "Beberapa panggilan ke memerlukan ('foo') tidak dapat menyebabkan kode modul dijalankan beberapa kali.". Ia mengatakan "tidak boleh", ia tidak mengatakan "tidak akan", jadi menanyakan bagaimana memastikan contoh modul dibuat hanya sekali dalam aplikasi adalah pertanyaan yang valid dari sudut pandang saya.
xorcus
9
Ini menyesatkan bahwa ini adalah jawaban yang benar untuk pertanyaan ini. Seperti yang ditunjukkan @mike di bawah ini, ada kemungkinan bahwa modul dimuat lebih dari sekali dan Anda memiliki dua instance. Saya mengalami masalah di mana saya hanya memiliki satu salinan Knockout tetapi dua contoh dibuat karena modul dimuat dua kali.
dgaviola
26

Tidak. Saat cache modul Node gagal, pola tunggal itu gagal. Saya memodifikasi contoh untuk berjalan dengan penuh makna di 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());

Ini memberikan keluaran yang diantisipasi oleh penulis:

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

Tetapi modifikasi kecil mengalahkan caching. Di OSX, lakukan ini:

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

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

Atau, di Linux:

% ln singleton.js singleton2.js

Kemudian ubah sg2baris yang diminta menjadi:

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

Dan bam , singleton dikalahkan:

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

Saya tidak tahu cara yang dapat diterima untuk menyiasati ini. Jika Anda benar-benar merasa perlu untuk membuat sesuatu seperti tunggal dan tidak keberatan mencemari namespace global (dan banyak masalah yang dapat terjadi), Anda dapat mengubah penulis getInstance()dan exportsbaris menjadi:

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

module.exports = singleton.getInstance();

Meski begitu, saya tidak pernah mengalami situasi di sistem produksi di mana saya perlu melakukan hal seperti ini. Saya juga tidak pernah merasa perlu menggunakan pola tunggal di Javascript.

Allen Luce
sumber
21

Melihat sedikit lebih jauh di Modul Caching Peringatan di modul docs:

Modul di-cache berdasarkan nama file yang diselesaikan. Karena modul dapat menyelesaikan ke nama file yang berbeda berdasarkan lokasi modul pemanggil (memuat dari folder node_modules), itu bukan jaminan bahwa require ('foo') akan selalu mengembalikan objek yang sama persis, jika itu akan menyelesaikan ke file yang berbeda .

Jadi, tergantung di mana Anda berada saat membutuhkan modul, dimungkinkan untuk mendapatkan contoh modul yang berbeda.

Sepertinya modul bukanlah solusi sederhana untuk menciptakan lajang.

Edit: Atau mungkin mereka adalah . Seperti @mkoryak, saya tidak dapat menemukan kasus di mana satu file dapat diselesaikan ke nama file yang berbeda (tanpa menggunakan symlink). Tetapi (seperti komentar @JohnnyHK), beberapa salinan file di node_modulesdirektori yang berbeda akan dimuat dan disimpan secara terpisah.

mike
sumber
ok, saya membacanya 3 kali dan saya masih tidak bisa memikirkan contoh di mana itu akan menyelesaikan ke nama file yang berbeda. Tolong?
mkoryak
1
@mkoryak Saya pikir itu mengacu pada kasus di mana Anda memiliki dua modul berbeda yang Anda perlukan dari node_modulesmana masing-masing bergantung pada modul yang sama, tetapi ada salinan terpisah dari modul dependen itu di bawah node_modulessubdirektori dari masing-masing dari dua modul yang berbeda.
JohnnyHK
@seperti Anda di sini bahwa modul itu dibuat beberapa kali ketika direferensikan melalui jalur yang berbeda. Saya menemukan kasus ini saat menulis pengujian unit untuk modul server. Saya perlu contoh tunggal. bagaimana cara mencapainya?
Sushil
Contohnya mungkin jalur relatif. misalnya. Diberikan require('./db')dalam dua file terpisah, kode untuk dbmodul dijalankan dua kali
ditulis
9
Saya baru saja mengalami bug parah karena sistem modul node tidak case-insenstivie. saya menelepon require('../lib/myModule.js');dalam satu file dan file require('../lib/mymodule.js');lain dan tidak mengirimkan objek yang sama.
heyarne
18

Singleton di node.js (atau di browser JS, dalam hal ini) seperti itu sama sekali tidak diperlukan.

Karena modul di-cache dan stateful, contoh yang diberikan pada tautan yang Anda berikan dapat dengan mudah ditulis ulang dengan lebih sederhana:

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
Paul Armstrong
sumber
5
Docs mengatakan " mungkin tidak menyebabkan kode modul dijalankan beberapa kali", jadi ada kemungkinan bahwa itu akan dipanggil beberapa kali, dan jika kode ini dijalankan lagi, socketList akan diatur ulang ke daftar kosong
Jonathan.
3
@Jonathan. Konteks dalam dokumen di sekitar kutipan itu tampaknya membuat kasus yang cukup meyakinkan yang mungkin tidak digunakan dalam gaya RFC TIDAK HARUS .
Michael
6
@Michael "mungkin" adalah kata lucu seperti itu. Bayangkan memiliki kata yang ketika dinegasikan berarti "mungkin tidak" atau "pasti tidak" ..
OJFord
1
The may notberlaku ketika Anda npm linkmodul lain selama pengembangan. Jadi berhati-hatilah saat menggunakan modul yang mengandalkan satu instance seperti eventBus.
mediafreakch
10

Satu-satunya jawaban di sini yang menggunakan kelas ES6

// SummaryModule.js
class Summary {

  init(summary) {
    this.summary = summary
  }

  anotherMethod() {
    // do something
  }
}

module.exports = new Summary()

membutuhkan singleton ini dengan:

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

Satu-satunya masalah di sini adalah Anda tidak dapat meneruskan params ke konstruktor kelas tetapi itu dapat dielakkan dengan memanggil initmetode secara manual .

danday74
sumber
pertanyaannya adalah "apakah lajang dibutuhkan", bukan "bagaimana Anda menulisnya"
mkoryak
@ danday74 Bagaimana kita dapat menggunakan contoh yang sama summarydi kelas lain, tanpa menginisialisasi lagi?
M Faisal Hameed
1
Di Node.js hanya memerlukannya di file lain ... const summary = require ('./ SummaryModule') ... dan itu akan menjadi instance yang sama. Anda dapat mengujinya dengan membuat variabel anggota dan menyetel nilainya di satu file yang memerlukannya, lalu mendapatkan nilainya di file lain yang memerlukannya. Itu harus menjadi nilai yang ditetapkan.
danday74
9

Anda tidak memerlukan sesuatu yang khusus untuk melakukan singleton di js, kode dalam artikel tersebut bisa jadi:

var socketList = {};

module.exports = {
      add: function() {

      },

      ...
};

Di luar node.js (misalnya, di browser js), Anda perlu menambahkan fungsi pembungkus secara manual (ini dilakukan secara otomatis di node.js):

var singleton = function() {
    var socketList = {};
    return {
        add: function() {},
        ...
    };
}();
Esailija
sumber
Seperti yang ditunjukkan oleh @Allen Luce, jika cache node gagal, pola tunggal juga gagal.
Rake
6

Lajang baik-baik saja di JS, mereka tidak perlu terlalu bertele-tele.

Dalam node jika Anda memerlukan singleton, misalnya untuk menggunakan instance ORM / DB yang sama di berbagai file di lapisan server Anda, Anda dapat memasukkan referensi ke dalam variabel global.

Cukup tulis modul yang membuat var global jika tidak ada, lalu kembalikan referensi ke sana.

@ allen-luce benar dengan contoh kode catatan kaki yang disalin di sini:

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

module.exports = singleton.getInstance();

tetapi penting untuk dicatat bahwa penggunaan newkata kunci tidak diperlukan. Objek lama, fungsi, iife, dll. Akan berfungsi - tidak ada voodoo OOP yang terjadi di sini.

poin bonus jika Anda menutup beberapa obj di dalam fungsi yang mengembalikan referensi ke sana, dan membuat fungsi itu menjadi global - bahkan penugasan ulang variabel global tidak akan mengganggu instance yang sudah dibuat darinya - meskipun ini berguna untuk dipertanyakan.

Adam Tolley
sumber
Anda tidak membutuhkan semua itu. Anda bisa melakukannya module.exports = new Foo()karena module.exports tidak akan dijalankan lagi, kecuali Anda melakukan sesuatu yang sangat bodoh
mkoryak
Anda benar-benar harus TIDAK bergantung pada efek samping implementasi. Jika Anda memerlukan satu instance, cukup ikat ke global, jika implementasinya berubah.
Adam Tolley
Jawaban di atas juga merupakan kesalahpahaman dari pertanyaan awal seperti 'Haruskah saya menggunakan lajang di JS atau apakah bahasa membuatnya tidak diperlukan?', Yang tampaknya juga menjadi masalah dengan banyak jawaban lainnya. Saya mendukung rekomendasi saya untuk tidak menggunakan implementasi require sebagai pengganti implementasi tunggal yang tepat dan eksplisit.
Adam Tolley
1

Menjaga agar tetap sederhana.

foo.js

function foo() {

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

  return bar;
};

module.exports = foo();

Lalu baru saja

var foo = require(__dirname + 'foo');
foo.doSomething('Hello', function(result){ console.log(result); });
Makan di Joes
sumber
pertanyaannya adalah "apakah lajang dibutuhkan", bukan "bagaimana Anda menulisnya"
mkoryak