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

ada*_*eck 150 javascript architecture constructor node.js promise

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

所以我的问题是,让我的构造函数返回一个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();
});
Run Code Online (Sandbox Code Playgroud)

现在,用户可能还没有提供补充Promise链链接:

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

// ERROR
// engine will not be available as an Engine object here
Run Code Online (Sandbox Code Playgroud)

这可能会造成问题,因为用户可能会感到困惑,为什么 engine 在施工后无法使用.

在构造函数中使用Promise的原因是有道理的.我希望整个博客在构建阶段后正常运行.然而,在呼叫之后,它似乎几乎无法立即访问该对象new.

我一直在争论使用的东西,engine.start().then()或者engine.init()会返回Promise.但那些看起来也很臭.

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

Ber*_*rgi 188

是的,这是一个不好的做法.构造函数应该返回其类的实例,没有别的.否则会破坏new运算符和继承.

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

如果我想从构造函数中执行某些操作会怎么样?

这应该是你班级的一种方法.你想改变全球状态?然后显式调用该过程,而不是生成对象的副作用.实例化后,此调用可以立即执行:

var engine = new Engine()
engine.displayPosts();
Run Code Online (Sandbox Code Playgroud)

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

如果我想异步加载数据到我的实例怎么办?

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

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

然后,使用静态方法加载数据,从中返回一个promise.然后将一个包含数据的调用链接到新实例中:

Engine.load({path: '/path/to/posts'}).then(function(posts) {
    new Engine(posts).displayPosts();
});
Run Code Online (Sandbox Code Playgroud)

这使得获取数据的方式具有更大的灵活性,并且大大简化了构造函数.同样,您可以编写返回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);
    });
});
Run Code Online (Sandbox Code Playgroud)

  • @mightyiam好吧,当`(new Engine)instanceof Engine`为false时,这肯定是出乎意料的.类似地,当你尝试从该类继承时,`super(...)`使用promise而不是引擎实例初始化`this` - 一团糟.不要从构造函数"返回". (6认同)

pha*_*aux 12

我遇到了同样的问题,想出了这个简单的解决方案.

而不是从构造函数返回Promise,将它放在this.initialization属性中,如下所示:

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

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

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

它从API消费者的角度看起来如何:

engine = new Engine({path: '/path/to/posts'})
engine.showPostsOnPage()
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为您可以向承诺注册多个回调,它们在结算后运行,或者如果已经解决,则在附加回调时运行.

这就是mongoskin的工作原理,除了它实际上并没有使用promises.


编辑:因为我写了这个回复,我已经爱上了ES6/7语法,所以还有另一个使用它的例子.今天你可以和babel一起使用它.

class Engine {

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

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

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

}
Run Code Online (Sandbox Code Playgroud)

编辑:您可以在节点7和--harmony标志中原生使用此模式!

  • 嗯,我不喜欢这种模式,因为需要"*包装每个方法*".这大多数时候都是不必要的开销,并且当方法确实返回promises时会使许多事情复杂化,而通常不需要. (5认同)
  • 我创建了 npm 模块,使包装自动:https://www.npmjs.com/package/synchronisify (3认同)
  • 我知道这是一个旧线程,但是为了避免“包装每个方法”问题,至少在 Node 中,代理很有用。 (2认同)

小智 8

为避免关注点分离,请使用工厂来创建对象。

class Engine {
    constructor(data) {
        this.data = data;
    }

    static makeEngine(pathToData) {
        return new Promise((resolve, reject) => {
            getData(pathToData).then(data => {
              resolve(new Engine(data))
            }).catch(reject);
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 避免 [`Promise` 构造函数反模式](http://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! (6认同)