有一个构造函数返回一个Promise是不好的做法吗?
我正在尝试为博客平台创build一个构造函数,并且里面有许多asynchronous操作。 这些范围从抓取目录中的post,parsing它们,通过模板引擎发送它们等等。
所以我的问题是,让我的构造函数返回一个承诺,而不是他们所谓的new
function的对象是不明智的。
例如:
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(); });
现在,用户也可能不提供补充Promise链接:
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()
这样的方法来代替Promise。 但是那些也似乎是臭的。
编辑:这是在一个Node.js项目。
是的,这是一个不好的做法。 构造函数应该返回它的类的一个实例,没有别的。 这会弄乱new
运营商 ,否则inheritance。
而且,一个构造函数只应该创build并初始化一个新的实例。 它应该设置数据结构和所有特定于实例的属性,但不执行任何任务。 如果可能的话,它应该是一个没有副作用的纯函数 ,具有所有的好处。
如果我想从我的构造函数执行的东西呢?
这应该在你的class级的方法。 你想改变全局状态? 然后明确地调用该过程,而不是作为生成对象的副作用。 这个调用可以在实例化之后进行:
var engine = new Engine() engine.displayPosts();
如果这个任务是asynchronous的,你现在可以很容易地从方法中返回一个promise的结果,很容易等到它完成。
但是,如果方法(asynchronous)改变实例和其他方法依赖的方式,我会不推荐这种模式,因为这会导致它们被要求等待(即使它们实际上是同步的,也会变成asynchronous),而且你很快就会一些内部队列pipe理正在进行。 不要编码实例存在,但实际上是不可用的。
如果我想asynchronous加载数据到我的实例呢?
问问自己: 你真的需要没有数据的实例吗? 你能以某种方式使用它吗?
如果答案是否定的 ,那么在获得数据之前不应该创build它。 将数据作为parameter passing给构造函数,而不是告诉构造函数如何获取数据(或传递数据承诺)。
然后,使用静态方法来加载数据,从中返回一个承诺。 然后将包装数据的调用链接到一个新的实例上:
Engine.load({path: '/path/to/posts'}).then(function(posts) { new Engine(posts).displayPosts(); });
这使获取数据的方式具有更大的灵活性,并简化了构造函数。 同样,你可以编写一些静态工厂函数来返回Engine
实例的promise:
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); }); });
我遇到了同样的问题,并提出了这个简单的解决scheme。
把它放在这个this.initialization
属性中,而不是从构造函数中返回一个Promise,如下所示:
function Engine(path) { var engine = this engine.initialization = Promise.resolve() .then(function () { return doSomethingAsync(path) }) .then(function (result) { engine.resultOfAsyncOp = result }) }
然后,将每个方法包装在初始化后运行的callback中,如下所示:
Engine.prototype.showPostsOnPage = function () { return this.initialization.then(function () { // actual body of the method }) }
它从API消费者angular度看起来如何:
engine = new Engine({path: '/path/to/posts'}) engine.showPostsOnPage()
这是有效的,因为你可以注册多个callback到一个承诺,他们运行后,或者,如果它已经解决,在附加callback时。
这是mongoskin如何工作,除非它实际上没有使用承诺。
编辑:因为我写了这个答复,我已经爱上了ES6 / 7的语法,所以还有另一个例子使用它。 你今天可以用babel使用它。
class Engine { constructor(path) { this.initialization = (async () => { this.resultOfAsyncOp = await doSomethingAsync(path) })() } async showPostsOnPage() { await this.initialization // actual body of the method } }
编辑 :你可以用节点7和--harmony
标志原生地使用这个模式!
为了避免问题的分离,请使用工厂创build对象。
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); }); } }
构造函数的返回值replace了new运算符刚生成的对象,因此返回promise不是一个好主意。 以前,来自构造函数的显式返回值被用于单例模式。
ECMAScript 2017中更好的方法是使用静态方法:您有一个进程,即静态数字。
在构造函数之后,在新对象上运行的哪个方法可能只能被类本身所知晓。 要将其封装在类中,可以使用process.nextTick或Promise.resolve,推迟进一步的执行,允许在Process.launch(构造函数的调用者)中添加侦听器以及其他内容。
由于几乎所有的代码都在Promise中执行,所以错误最终会在Process.fatal中出现
这个基本的想法可以修改,以适应特定的封装需求。
class MyClass { constructor(o) { if (o == null) o = false if (o.run) Promise.resolve() .then(() => this.method()) .then(o.exit).catch(o.reject) } async method() {} } class Process { static launch(construct) { return new Promise(r => r( new construct({run: true, exit: Process.exit, reject: Process.fatal}) )).catch(Process.fatal) } static exit() { process.exit() } static fatal(e) { console.error(e.message) process.exit(1) } } Process.launch(MyClass)