构造函数中的异步操作

IT技术 javascript promise prototype
2021-01-13 17:57:29

嘿,我对函数中的原型和继承有疑问。你能解释一下我如何从构造函数返回 arr 并将这个 arr 添加到原型中吗?

var example = new Constructor()
function Constructor(){
   Service.getService().then(function(data){
      this.arr = data.data.array;
      return this.arr
   })
}

Constructor.prototype.getArray = function(){
   console.log(this.arr)
})
example.getArray();

而在getArraythis.arr 中是未定义的。Service and getService()是角度工厂和前端和后端之间的连接

1个回答

在构造函数中放置异步操作特别困难。这有几个原因:

  1. 构造函数需要返回新创建的对象,因此它不能返回会告诉您异步操作何时完成的Promise。
  2. 如果在构造函数中执行异步操作来设置一些实例数据并且构造函数返回对象,那么调用代码将无法知道异步操作何时实际完成。

由于这些原因,您通常不想在构造函数中执行异步操作。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(),它将这两个步骤组合成一个函数(上面第二种设计模式的说明)。netmodule实例并不碰巧使用诺言(很少有原创的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
   });
}
添加了 eventEmitter 设计模式。
2021-03-12 17:57:29
@Mat.Now - 总是有更多的东西需要学习。我想说的是,真正了解 Javascript 中的异步操作是您在 Javascript 开发中从初学者到更高水平的方式。了解时序是如何工作的以及设计工具用于处理时序是提升到更高水平的一个大问题。这是关于堆栈溢出的一个非常非常常见的主题(初学者或中级开发人员的常见问题),因此有很多相关的问题。按照此处的这些类型问题进行学习,然后尝试自己回答一些问题。
2021-03-16 17:57:29
如果您使用的是工厂函数方法(也可以是static方法),您可能应该向 的构造函数添加另一个参数.asyncProp,并避免使用该init方法。
2021-03-16 17:57:29
非常感谢您的解释。现在我终于明白它是如何工作的了。顺便提一句。这个知识小辈应该知道吧?因为我是一年前开始学javascript的,不知道在哪里呵呵。
2021-03-31 17:57:29
我认为这initPromise是一个比事件监听器更好的主意。使用 promise,您可以随时使用.then(),而不必检查init事件是否已经触发(这只会导致 zalgo)。
2021-03-31 17:57:29