我正在尝试为博客平台创建一个构造函数,并且内部进行了许多异步操作。这些范围包括从目录中获取帖子,对其进行解析,通过模板引擎发送它们,等等。
所以我的问题是,让我的构造函数返回一个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();
});
现在,用户可能还不提供补充的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
运算符和继承。
此外,构造函数应仅创建和初始化新实例。它应该设置数据结构和所有特定于实例的属性,但不执行任何任务。如果可能的话,它应该是一个纯函数,没有副作用,并具有所有好处。
如果我想从构造函数中执行操作怎么办?
那应该放在您的类的方法中。您想改变全球状态?然后显式调用该过程,而不是作为生成对象的副作用。该调用可以在实例化之后立即进行:
var engine = new Engine()
engine.displayPosts();
如果该任务是异步的,那么您现在可以轻松地从方法中返回其结果的承诺,轻松地等待直到完成为止。
但是,当方法(异步)使实例发生突变并且其他方法依赖于该实例时,我将不推荐这种模式,因为这将导致它们需要等待(即使它们实际上是同步的,也要成为异步的),并且您很快就会拥有一些内部队列管理正在进行。不要编写实例存在但实际上不可用的代码。
如果要异步将数据加载到实例中怎么办?
问问自己:您实际上是否需要没有数据的实例?你能以某种方式使用它吗?
如果答案是没有,那么你不应该你有数据之前创建它。将数据本身作为构造函数的参数,而不是告诉构造函数如何获取数据(或传递对数据的承诺)。
然后,使用静态方法加载数据,并从中返回承诺。然后链接一个将数据包装在新实例上的调用:
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);
});
});
我遇到了同样的问题,并提出了这个简单的解决方案。
而不是从构造函数返回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()
之所以行之有效,是因为您可以将多个回调注册到一个Promise,并且它们可以在解析后运行,也可以在附加回调时运行(如果已经解析)。
这是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
}
}
编辑:您可以将本模式与节点7和--harmony
标志一起使用!
为避免关注点分离,请使用工厂创建对象。
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);
});
}
}
构造函数的返回值替换了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)
文章标签:architecture , constructor , javascript , node.js , promise
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!
评论已关闭!