es6 classes:父类中的fetch,参考子类中的resolved fetch

Posted

技术标签:

【中文标题】es6 classes:父类中的fetch,参考子类中的resolved fetch【英文标题】:es6 classes: Fetch in the parent class, refer to resolved fetch in child class 【发布时间】:2017-01-16 14:57:07 【问题描述】:

假设我在spanish.json 中有这些数据:

[
   "word": "casa", "translation": "house",
   "word": "coche", "translation": "car",
   "word": "calle", "translation": "street"
]

我有一个 Dictionary 类来加载它 并添加搜索方法:

// Dictionary.js
class Dictionary 
  constructor(url)
    this.url = url;
    this.entries = []; // we’ll fill this with a dictionary
    this.initialize();
  

  initialize()
    fetch(this.url)
      .then(response => response.json())
      .then(entries => this.entries = entries)
  

  find(query)
    return this.entries.filter(entry => 
      entry.word == query)[0].translation
  

我可以实例化它,并用它来查找“calle” 使用这个小单页应用程序:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>spanish dictionary</title>
</head>
<body>

<p><input placeholder="Search for a Spanish word" type="">  
<p><output></output>

<script src=Dictionary.js></script>
<script>

  let es2en = new Dictionary('spanish.json')
  console.log(es2en.find('calle')) // 'street'

  input.addEventListener('submit', ev => 
    ev.preventDefault();
    let translation = dictionary.find(ev.target.value);
    output.innerHTML = translation;
  )

</script>


</body>
</html>

到目前为止一切顺利。但是,假设我想继承 Dictionary 并添加一个计算所有单词的方法并添加 计数到页面。 (伙计,我需要一些投资者。)

所以,我获得了另一轮资金并实施CountingDictionary

class CountingDictionary extends Dictionary 
  constructor(url)
    super(url)
  

  countEntries()
    return this.entries.length
  

新的单页应用:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Counting Spanish Dictionary</title>
</head>
<body>

<p><input placeholder="Search for a Spanish word" type="">  
<p><output></output>

<script src=Dictionary.js></script>
<script>


  let 
    es2en = new CountingDictionary('spanish.json'),
    h1 = document.querySelector('h1'),
    input = document.querySelector('input'),
    output = document.querySelector('output');

  h1.innerHTML = es2en.countEntries();

  input.addEventListener('input', ev => 
    ev.preventDefault();
    let translation = es2en.find(ev.target.value);
    if(translation)
      output.innerHTML = `$translation`;
  )

</script>

</body>
</html>

当此页面加载时,h1 将填充有 0

我知道我的问题是什么,我只是不知道如何解决它。

问题是fetch调用返回一个Promise, 并且 .entries 属性仅填充了数据 一旦 Promise 返回,从 URL 中获取。直到那时, .entries 仍然为空。

如何让.countEntries 等待获取承诺解决?

或者有没有更好的方法来完全实现我想要的?

【问题讨论】:

【参考方案1】:

问题是fetch调用返回一个Promise, 并且 .entries 属性仅填充了数据 一旦 Promise 返回,从 URL 中获取。直到那时, .entries 仍为空。

您需要做出entries 的承诺。这样一来,您的所有方法都必须返回 Promise,但 Dictionary 实例可以立即使用。

class Dictionary 
  constructor(url) 
    this.entriesPromise = fetch(url)
      .then(response => response.json())
  
  find(query) 
    return this.entriesPromise.then(entries => 
       var entry = entries.find(e => e.word == query);
       return entry && entry.translation;
    );
  

class CountingDictionary extends Dictionary 
  countEntries() 
    return this.entriesPromise.then(entries => entries.length);
  

let es2en = new CountingDictionary('spanish.json'),
    h1 = document.querySelector('h1'),
    input = document.querySelector('input'),
    output = document.querySelector('output');

es2en.countEntries().then(len => 
  fh1.innerHTML = len;
);
input.addEventListener(ev => 
  ev.preventDefault();
  es2en.find(ev.target.value).then(translation => 
    if (translation)
      output.innerHTML = translation;
  );
);

或者有没有更好的方法来完全实现我想要的?

是的。看看Is it bad practice to have a constructor function return a Promise?。

class Dictionary 
  constructor(entries) 
    this.entries = entries;
    
  static load(url) 
    return fetch(url)
      .then(response => response.json())
      .then(entries => new this(entries));
  

  find(query) 
    var entry = this.entries.find(e => e.word == query);
    return entry && entry.translation;
  

class CountingDictionary extends Dictionary 
  countEntries() 
    return this.entries.length;
  

let es2enPromise = CountingDictionary.load('spanish.json'),
    h1 = document.querySelector('h1'),
    input = document.querySelector('input'),
    output = document.querySelector('output');

es2enPromise.then(es2en => 
  fh1.innerHTML = es2en.countEntries();
  input.addEventListener(…);
);

如您所见,与包含 Promise 的实例相比,这种方法需要更少的整体嵌套。实例的承诺也可以更好地组合,例如当您需要在安装侦听器和显示输出之前等待 domready 时,您将能够获得对 DOM 的承诺,并且可以使用 Promise.all 等待两者。

【讨论】:

感谢您的评论。在我看来,这种方法的问题在于,您最终仍然会得到一个加载方法,该方法本质上是类外部的。虽然将 .load 放在类的静态方法中进行了一些封装,但调用该方法的模式与仅在全局命名空间中运行 fetch(url),然后使用该数据实例化类仍然没有什么不同. 是的,有点不同,但这是正确的做法 :-) 您总是需要“在外部”等待。 load 方法必须是静态的有什么原因吗? @pat 它为一个实例返回一个promise,它不能在一个还不存在的实例上调用。【参考方案2】:

您必须将fetch() 调用的结果分配给某个变量,例如:

initialize()
  this.promise = fetch(this.url)
    .then(response => response.json())
    .then(entries => this.entries = entries)

然后就可以调用then()方法了:

let es2en = new CountingDictionary('spanish.json'),
    h1 = document.querySelector('h1'),
    input = document.querySelector('input'),
    output = document.querySelector('output');

es2en.promise.then(() => h1.innerHTML = es2en.countEntries())

input.addEventListener('input', ev => 
  ev.preventDefault();
  let translation = es2en.find(ev.target.value);
  if(translation)
    output.innerHTML = `$translation`;
)

【讨论】:

这和Frxstrem的反应很相似,所以也有同样的缺点。【参考方案3】:

一个简单的解决方案:在你做fetch()之后信守承诺,然后添加一个ready()方法,让你等到类完全初始化:

class Dictionary 
  constructor(url)
    /* ... */

    // store the promise from initialize() [see below]
    // in an internal variable
    this.promiseReady = this.initialize();
  

  ready() 
    return this.promiseReady;
  

  initialize() 
    // let initialize return the promise from fetch
    // so we know when it's completed
    return fetch(this.url)
      .then(response => response.json())
      .then(entries => this.entries = entries)
  

  find(query)  /* ... */ 

然后,您只需在构建对象后调用.ready(),您就会知道它何时加载:

let es2en = new CountingDictionary('spanish.json')
es2en.ready()
  .then(() => 
    // we're loaded and ready
    h1.innerHTML = es2en.countEntries();
  )
  .catch((error) => 
    // whoops, something went wrong
  );

还有一点额外的好处,您可以使用.catch 来检测加载过程中发生的错误,例如网络错误或未捕获的异常。

【讨论】:

ready()方法的作用是什么,什么时候可以直接访问promiseReady @Gothdo:老实说,这主要是个人约定,但想法是仅通过 getter 公开(因此不能从外部修改),我认为拥有一个返回的函数更有意义作为“动作”的承诺,(即“等待此类准备就绪”)而不是直接访问。 这让我想起了这种方法:quickleft.com/blog/leveraging-deferreds-in-backbonejs。虽然从 fetch 调用在类内部的意义上说是“自包含的”(并且这里 Dictionary 因此使用 url 作为构造函数参数),但缺点是任何使用类实例的客户端都必须参考“永远”的 .ready() 方法……在我看来,这使得 .initialize() 似乎并没有真正初始化……

以上是关于es6 classes:父类中的fetch,参考子类中的resolved fetch的主要内容,如果未能解决你的问题,请参考以下文章

super()方法详解

在子类中使用啥关键字做前缀可调用被子类覆盖的父类中的方法

ES6 浅谈class继承机制

如何在 ES6 类中使用 this 的帮助来选择所有元素

php父类中访问子类的静态属性

在子类中使用啥关键字做前缀可调用被子类覆盖的父类中的方法