【问题标题】:Asynchronous operations in constructor构造函数中的异步操作
【发布时间】:2018-09-29 00:26:56
【问题描述】:

嘿,我对函数中的原型和继承有疑问。你能解释一下我如何从构造函数返回 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();

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

【问题讨论】:

  • 在你的 Promise 回调中使用 .bind 或使用箭头函数。
  • 您可能还必须等待承诺解决,然后值才能存在。
  • 您立即致电example.getArray(),但getService() 可能尚未解决并分配this.arr...
  • 最好将promise请求放在getArray中,否则@Aaron可以避免上述问题
  • 谢谢,我使用了 .bind(this) 并在 getArray() 中看到了 arr 但如果我尝试显示 this.arr 我会得到未定义

标签: javascript promise prototype


【解决方案1】:

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

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

由于这些原因,您通常不希望在构造函数中执行异步操作。 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
   });
}

【讨论】:

  • 非常感谢您的解释。现在我终于明白它是如何工作的了。顺便提一句。这个知识小辈应该知道吧?因为我一年前就开始学习javascript了,不知道自己在哪里嘿嘿。
  • @Mat.Now - 总是有更多的东西要学。我想说真正理解 Javascript 中的异步操作是您如何从初学者毕业到 Javascript 开发的更高级别。了解时序的工作原理以及用于处理时序的设计工具对于提升到更高水平非常重要。这是关于堆栈溢出的一个非常非常常见的话题(初学者或中级开发人员的常见问题),因此有很多与它相关的问题。在此处按照这些类型的问题进行学习,然后尝试自己回答一些问题。
  • 添加了 eventEmitter 设计模式。
  • 我认为initPromise 比事件监听器更好。通过 Promise,您可以随时使用 .then(),而不必检查 init 事件是否已经触发(这只会导致 zalgo)。
  • 如果您使用工厂函数方法(也可以是static 方法),您可能应该只为.asyncProp 的构造函数添加另一个参数,并避免使用init 方法.
猜你喜欢
  • 2017-09-01
  • 2017-04-14
  • 2012-08-05
  • 2020-10-25
  • 2016-06-15
  • 1970-01-01
  • 2014-07-19
相关资源
最近更新 更多