最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - es6 classes: Fetch in the parent class, refer to resolved fetch in child class - Stack Overflow

programmeradmin1浏览0评论

Suppose I have this data in spanish.json:

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

And I have a Dictionary class that loads it and adds a search method:

// 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
  }
}

And I can instantiate that, and use it to look up ‘calle’ with this little single-page app:

<!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>

So far so good. But, let’s say I want to subclass Dictionary and add a method that counts all the words and adds that count to the page. (Man, I need some investors.)

So, I get another round of funding and implement CountingDictionary:

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

  countEntries(){
    return this.entries.length
  }
}

The new single page app:

<!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>

When this page loads, the h1 gets populated with 0.

I know what my problem is, I just don’t how to fix it.

The problem is that the fetch call returns a Promise, and the .entries property is only populated with the data from the URL once that Promise has returned. Until then, .entries remains empty.

How can I make .countEntries wait for the fetch promise to resolve?

Or is there a better way entirely to achieve what I want here?

Suppose I have this data in spanish.json:

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

And I have a Dictionary class that loads it and adds a search method:

// 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
  }
}

And I can instantiate that, and use it to look up ‘calle’ with this little single-page app:

<!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>

So far so good. But, let’s say I want to subclass Dictionary and add a method that counts all the words and adds that count to the page. (Man, I need some investors.)

So, I get another round of funding and implement CountingDictionary:

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

  countEntries(){
    return this.entries.length
  }
}

The new single page app:

<!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>

When this page loads, the h1 gets populated with 0.

I know what my problem is, I just don’t how to fix it.

The problem is that the fetch call returns a Promise, and the .entries property is only populated with the data from the URL once that Promise has returned. Until then, .entries remains empty.

How can I make .countEntries wait for the fetch promise to resolve?

Or is there a better way entirely to achieve what I want here?

Share Improve this question edited Sep 8, 2016 at 15:44 Bergi 666k161 gold badges1k silver badges1.5k bronze badges asked Sep 8, 2016 at 15:29 user2467065user2467065
Add a ment  | 

3 Answers 3

Reset to default 5

The problem is that the fetch call returns a Promise, and the .entries property is only populated with the data from the URL once that Promise has returned. Until then, .entries remains empty.

You would need to make entries a promise. That way, all of your methods had to return promises, but the Dictionary instance is immediately usable.

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;
  });
});

Or is there a better way entirely to achieve what I want here?

Yes. Have a look at 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(…);
});

As you can see, this appraoch requires less overall nesting pared to an instance that contains promises. Also a promise for the instance is better posable, e.g. when you would need to wait for domready before installing the listeners and showing output you would be able to get a promise for the DOM and could wait for both using Promise.all.

You have to assign the result of the fetch() call to some variable, for example:

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

Then you can call the then() method on it:

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}`;
})

A simple solution: Keep the promise after you do fetch(), then add a ready() method that allows you to wait until the class has initialized pletely:

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 pleted
    return fetch(this.url)
      .then(response => response.json())
      .then(entries => this.entries = entries)
  }

  find(query) { /* ... */ }
}

Then you just call .ready() after you've constructed your object, and you'll know when it's loaded:

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

As a little extra advantage, you can just use .catch to detect errors that occur during loading, e.g., network errors or uncaught exceptions.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论