将异步操作放在构造函数中特别困难。这有几个原因:
- 构造函数需要返回新创建的对象,因此它不能返回一个可以告诉您异步操作何时完成的承诺。
- 如果您在构造函数中执行异步操作来设置一些实例数据并且构造函数返回对象,那么您无法让调用代码知道异步操作何时实际完成。
由于这些原因,您通常不希望在构造函数中执行异步操作。 IMO,下面最干净的架构是工厂函数,它返回一个解析为您完成的对象的承诺。您可以在工厂函数中执行尽可能多的异步操作(调用对象上的任何方法),并且在对象完全形成之前不要将对象公开给调用者。
以下是处理该问题的一些不同选项:
使用返回 Promise 的工厂函数
这使用了一个工厂函数,可以为您完成一些更常见的工作。它也不会在新对象完全初始化之前显示新对象,这是一种很好的编程习惯,因为调用者不会意外地尝试使用异步内容尚未完成的部分形成的对象。 factory 函数选项还通过拒绝返回的 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
});
如果您使用的是模块,则只能导出工厂函数(无需导出类本身),从而强制对象已正确初始化,并且在初始化完成之前不使用。
将异步对象初始化分解为可以返回承诺的单独方法
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 模块示例碰巧没有使用 Promise(很少有 nodejs 原始 api 使用),但它们使用回调和事件完成相同的逻辑。
代码的其他说明
您的代码中this 的值也可能存在问题。如果您将常规的function() {} 引用传递给.then() 处理程序,它自然不会从周围环境中保留this 的值。所以,在这个:
function Constructor(){
Service.getService().then(function(data){
this.arr = data.data.array;
return this.arr
})
}
当您尝试执行 this.arr = data.data.array; 时,this 的值将不正确。在 ES6 中解决该问题的最简单方法是使用粗箭头函数:
function Constructor(){
Service.getService().then(data => {
this.arr = data.data.array;
return this.arr
});
}