让构造函数返回 Promise 是不是不好的做法?

我正在尝试为博客平台创建一个构造函数,它内部有许多异步操作。这些范围包括从目录中抓取帖子,解析它们,通过模板引擎发送它们等。

所以我的问题是,让我的构造函数返回一个承诺而不是他们调用的函数的对象是不明智的。new

例如:

var engine = new Engine({path: '/path/to/posts'}).then(function (eng) {
   // allow user to interact with the newly created engine object inside 'then'
   engine.showPostsOnOnePage();
});

现在,用户也可能不提供补充承诺链链接:

var engine = new Engine({path: '/path/to/posts'});

// ERROR
// engine will not be available as an Engine object here

这可能会带来一个问题,因为用户可能会感到困惑,为什么在施工后不可用。engine

在构造函数中使用 Promise 的原因是有道理的。我希望整个博客在施工阶段后能够正常运行。但是,在呼叫后几乎无法访问该物体似乎是一种气味。new

我曾讨论过使用类似或会返回应许的东西。但这些似乎也很臭。engine.start().then()engine.init()

编辑:这是在 Node.js 项目中。


答案 1

是的,这是一种不好的做法。构造函数应返回其类的实例,而不是其他任何内容。否则,它会弄乱新的运算符和继承。

此外,构造函数应该只创建和初始化一个新实例。它应该设置数据结构和所有特定于实例的属性,但不能执行任何任务。如果可能的话,它应该是一个没有副作用的纯功能,具有所有的好处。

如果我想从构造函数执行操作,该怎么办?

这应该在你的类的方法中。您想改变全局状态吗?然后显式调用该过程,而不是作为生成对象的副作用。此调用可以在实例化后立即进行:

var engine = new Engine()
engine.displayPosts();

如果该任务是异步的,您现在可以轻松地从该方法返回其结果的承诺,以便轻松地等待它完成。
但是,当方法(异步)改变实例并且其他方法依赖于此时,我不建议使用此模式,因为这会导致它们需要等待(即使它们实际上是同步的,也要成为异步),并且您很快就会进行一些内部队列管理。不要将实例编码为存在,但实际上不可用。

如果我想将数据异步加载到实例中,该怎么办?

问问自己:您是否真的需要没有数据的实例?你能以某种方式使用它吗?

如果答案是否定的,则在获得数据之前不应创建它。将数据 ifself 作为构造函数的参数,而不是告诉构造函数如何获取数据(或传递数据的承诺)。

然后,使用静态方法加载数据,从中返回承诺。然后链接一个调用,该调用将数据包装在新实例中:

Engine.load({path: '/path/to/posts'}).then(function(posts) {
    new Engine(posts).displayPosts();
});

这为获取数据的方式提供了更大的灵活性,并大大简化了构造函数。同样,您可以编写静态工厂函数来返回实例的 promise:Engine

Engine.fromPosts = function(options) {
    return ajax(options.path).then(Engine.parsePosts).then(function(posts) {
        return new Engine(posts, options);
    });
};

…

Engine.fromPosts({path: '/path/to/posts'}).then(function(engine) {
    engine.registerWith(framework).then(function(framePage) {
        engine.showPostsOn(framePage);
    });
});

答案 2

我遇到了同样的问题,并提出了这个简单的解决方案。

与其从构造函数返回 Promise,不如将其放入属性中,如下所示:this.initialization

function Engine(path) {
  var engine = this
  engine.initialization = Promise.resolve()
    .then(function () {
      return doSomethingAsync(path)
    })
    .then(function (result) {
      engine.resultOfAsyncOp = result
    })
}
  

然后,将每个方法包装在初始化后运行的回调中,如下所示:

Engine.prototype.showPostsOnPage = function () {
  return this.initialization.then(function () {
    // actual body of the method
  })
}

从 API 使用者的角度来看,它的外观:

engine = new Engine({path: '/path/to/posts'})
engine.showPostsOnPage()

这是有效的,因为您可以将多个回调注册到一个承诺,并且这些回调在解析后运行,或者如果已经解析,则在附加回调时运行。

这就是mongoskin的工作方式,除了它实际上并没有使用承诺。


编辑:自从我写了那个回复以来,我已经爱上了ES6 / 7语法,所以还有另一个例子使用它。

class Engine {
  
  constructor(path) {
    this._initialized = this._initialize()
  }

  async _initialize() {
    // actual async constructor logic
  }

  async showPostsOnPage() {
    await this._initialized
    // actual body of the method
  }
  
}