在构造函数中放置异步操作特别困难。这有几个原因:
- 构造函数需要返回新创建的对象,因此它不能返回会告诉您异步操作何时完成的Promise。
- 如果在构造函数中执行异步操作来设置一些实例数据并且构造函数返回对象,那么调用代码将无法知道异步操作何时实际完成。
由于这些原因,您通常不想在构造函数中执行异步操作。IMO,下面最干净的架构是工厂函数,它返回一个可解析为您完成的对象的Promise。您可以在工厂函数中执行尽可能多的异步操作(调用对象上的任何方法),并且在对象完全形成之前不会将对象暴露给调用者。
这些是处理该问题的一些不同选择:
使用返回 Promise 的工厂函数
这使用了一个工厂函数,可以为您完成一些更常见的工作。在新对象完全初始化之前它也不会显示新对象,这是一个很好的编程实践,因为调用者不会意外地尝试使用异步内容尚未完成的部分形成的对象。工厂函数选项还通过拒绝返回的Promise干净地传播错误(同步或异步):
// don't make this class definition public so the constructor is not public
class MyObj() {
constructor(someValue) {
this.someProp = someValue;
}
init() {
return Service.getService().then(val => {
this.asyncProp = val;
return this;
});
}
}
function createMyObj(someValue) {
let x = new MyObj(someVal);
return x.init();
}
createMyObj(someVal).then(obj => {
// obj ready to use and fully initialized here
}).catch(err => {
// handle error here
});
如果您使用module,则可以仅导出工厂函数(无需导出类本身),从而强制对象正确初始化并且在初始化完成之前不使用。
将异步对象初始化分解为可以返回Promise的单独方法
class MyObj() {
constructor(someValue) {
this.someProp = someValue;
}
init() {
return Service.getService().then(val => {
this.asyncProp = val;
});
}
}
let x = new MyObj(someVal);
x.init().then(() => {
// ready to use x here
}).catch(err => {
// handle error
});
使用事件来表示完成
这个方案在很多 I/O 相关的 API 中都有使用。一般的想法是您从构造函数返回一个对象,但调用者知道该对象在特定事件发生之前尚未真正完成其初始化。
// object inherits from EventEmitter
class MyObj extends EventEmitter () {
constructor(someValue) {
this.someProp = someValue;
Service.getService().then(val => {
this.asyncProp = val;
// signal to caller that object has finished initializing
this.emit('init', val);
});
}
}
let x = new MyObj(someVal);
x.on('init', () => {
// object is fully initialized now
}).on('error', () => {
// some error occurred
});
将异步操作放入构造函数的黑客方法
虽然我不建议使用这种技术,但这是将异步操作放入实际构造函数本身所需要的:
class MyObj() {
constructor(someValue) {
this.someProp = someValue;
this.initPromise = Service.getService().then(val => {
this.asyncProp = val;
});
}
}
let x = new MyObj(someVal);
x.initPromise.then(() => {
// object ready to use now
}).catch(err => {
// error here
});
请注意,您会在各种 API 的许多地方看到第一个设计模式。例如,对于 node.js 中的套接字连接,您会看到:
let socket = new net.Socket(...);
socket.connect(port, host, listenerCallback);
套接字是在第一步中创建的,然后在第二步中连接到某些东西。然后,同一个库有一个工厂函数net.createConnection()
,它将这两个步骤组合成一个函数(上面第二种设计模式的说明)。该net
module实例并不碰巧使用诺言(很少有原创的NodeJS的API做),但它们完成使用回调和事件相同的逻辑。
关于您的代码的其他说明
您this
的代码中的值可能也有问题。一个.then()
处理程序不自然保护的valuethis
从周围环境中如果你传递一个普通function() {}
的参考。所以,在这个:
function Constructor(){
Service.getService().then(function(data){
this.arr = data.data.array;
return this.arr
})
}
this
当你尝试做的时候的valuethis.arr = data.data.array;
不会是正确的。在 ES6 中解决该问题的最简单方法是使用粗箭头函数:
function Constructor(){
Service.getService().then(data => {
this.arr = data.data.array;
return this.arr
});
}