如何以 Node.js 方式编写单例

如何以 Node.js 方式编写单例

如何以 Node.js 方式编写单例

文 / Victor - AfterShip

译 / 吴天成 - AfterShip

问题描述

以下面这种方式,写单例很容易:

let someModule

async getSomeModule() { if (!someModule) { someModule = await someAsyncOperationsToInitializeModule() } return someModule }

module.exports = getSomeModule

通常以这种方式使用它:

// in async function
const getSomeModule = require('./getSomeModule')
const someModule = await getSomeModule()

除非你希望将模块的加载延迟到初次运行时,否则不鼓励这种方式。

因为,这将带来很多没必要的分支代码(例如,if statement ),实际上我们希望避免这种代码。而且使用 let 语法将会中断静态代码分析,导致 IDE 不能正确推导出 someModule 的类型。

解决方案 A

请注意,node 的模块系统默认为单例(模块在第一次 required 的时候将会被缓存[1])。所以一旦一个 promiseresolved 并导出,无论谁 require(加载) 模块,它将始终返回第一次 resolved 的结果。

以下是只使用const来实现的方式:

// NodeJs 方式的 async 单例
// someAsyncOperationsToInitializeModule 为 async function
// 注意,此处执行函数,未 await
const someModule = someAsyncOperationsToInitializeModule()

module.exports = someModule

2 行代码,就够了。

你该如何使用这个模块呢?

// in async function
// 不要用 "await getSomeModule()", 你不需要 `()`
const getSomeModule = require('./getSomeModule')
const someModule = await getSomeModule

someModule 的值绝对与 [问题描述] 中提到的代码运行结果完全相同。

你可能会注意到文件名最好更改为 ./someModule.js./asyncSomeModule.js .

另外一个可能会提出的问题是,我们已经使用了 await getSomeModule() ,但是在当前方案中,被调整为了 await getSomeModule。如果我们采用这种解决方案,将会对现有代码造成很大的影响。

其实,只需要做一点点调整,就可以保持之前的文件命名和调用方式。

解决方案 B

// NodeJS 方式的 async 单例

const someModule = someAsyncOperationsToInitializeModule()

module.exports = () => someModule

现在,你无需改变任何外部代码。这种实现 100% 向后兼容。也就是说,你无需改造模块的调用方式,正如问题中所提到的调用方式一样。

// in async function
const getSomeModule = require('./getSomeModule')
const someModule = await getSomeModule()

Show me the Code

https://repl.it/[@victoratas](/user/victoratas)/Singleton-in-Nodejs

补充资料

[1] https://nodejs.org/api/modules.html#modules_caching


3 回复

感谢分享,不过老哥你这个主题我看着有点辛苦,背景是黑的,代码块大量文本是灰的,辨识度不高。这是您自己定义的主题吗?


为什么不直接 module.exports = await someModule
这样不就直接导出一个实例了么?

在 Node.js 中,实现单例模式(Singleton Pattern)主要是为了确保一个类只有一个实例,并提供一个全局访问点。以下是一个简单的示例,展示了如何使用 Node.js 编写单例模式:

// Singleton.js
class Singleton {
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    this.value = Math.random(); // 示例属性
    Singleton.instance = this;
    // 私有方法或属性可以放在这里
    Object.freeze(this); // 防止修改实例
  }

  // 示例方法
  getValue() {
    return this.value;
  }

  // 静态方法用于获取实例
  static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
}

// 使用示例
const singleton1 = Singleton.getInstance();
const singleton2 = new Singleton(); // 这将返回已经存在的实例

console.log(singleton1 === singleton2); // 输出: true
console.log(singleton1.getValue());    // 输出一个随机数

在这个示例中,Singleton 类通过在其构造函数中检查是否已经存在一个实例来确保只有一个实例被创建。如果实例已经存在,构造函数会返回已经存在的实例。此外,getInstance 静态方法提供了一个全局访问点来获取单例实例。

使用 Object.freeze 可以防止实例被修改,这是一种保护单例状态的常用方法。不过,请注意,这不会阻止通过原型链上的方法修改对象。

回到顶部