Objetivo: quiero crear una clase que aborde una interfaz de noticias y represente los datos en la vista. Mi clase ( MyNews ) tiene varios métodos y un método es fetchNews() . En este método obtengo los datos de la API. Los datos de la API deben asignarse en una variable de clase ( news ). En otro método ( showNews() ) quiero acceder a los datos para iterar sobre ellos.
Mi problema: soy principiante en js y me pregunto cómo consultar los datos. Según tengo entendido, el flujo es así: el constructor llama a fetchNews fetch news primero recibe una promesa y espera hasta que se resuelva la promesa, luego los datos se asignan al miembro pero el JS asíncrono se está ejecutando js no espera en este punto hasta la promesa se resuelve y continúa trabajando con la matriz vacía. Este es exactamente mi problema.
Pregunta ¿Qué debo hacer o cambiar para obtener los datos correctamente? Para que pueda construir una lista HTML, por ejemplo.
Mi código JS
class MyNews { news = []; apiUrl = "https://jsonplaceholder.typicode.com/posts"; constructor() { this.news; this.fetchNews(); } fetchNews() { fetch(this.apiUrl) .then(async (data) => { this.news = await data.json(); console.log("A:",this.news.length); // i have results }) .catch((err) => { console.log(err) }); // ... } showNews() { console.log("B:",this.news.length); // empty array return this.news; } } n = new MyNews; console.log("C:", n.showNews().length );Sugeriría agregar una función getAndShowNews() a su clase.
Luego nos aseguraremos de devolver la promesa de la llamada de búsqueda fetchNews() , para que podamos await en otra función asíncrona.
En la función getAndShowNews() () esperaremos el resultado de fetchNews() (una promesa), esto asegurará que se llenen las noticias. Luego mostraremos las noticias recorriendo cada elemento.
Podría hacer esto en una función fuera de la clase, pero parece tener sentido hacerlo en la clase.
class MyNews { news = []; apiUrl = "https://jsonplaceholder.typicode.com/posts"; constructor() { this.fetchNews(); } fetchNews() { return fetch(this.apiUrl) .then(async (data) => { this.news = await data.json(); }) .catch((err) => { console.log(err) }); // ... } showNews(newsElementId, count = 8) { let newsElement = document.getElementById(newsElementId); for(let newsItem of this.news.slice(0,count)) { let el = document.createElement('li'); el.innerText = newsItem.title; newsElement.appendChild(el); } } async getAndShowNews(newsElementId) { // Wait for the fetchNews() call to complete... await this.fetchNews(); // Show the news... this.showNews(newsElementId); } } n = new MyNews(); n.getAndShowNews('news'); <b>News</b> <ol id='news'> </ol>El problema es que fetch() es una función asíncrona. Esto es lo que realmente sucede:
Lo primero que viene a la mente es usar la palabra clave await o usar promesas directamente. Sin embargo, esto no resuelve completamente su problema, porque el constructor no puede ser una función asíncrona.
Recomiendo esta solución:
Mueva la llamada de fetchNews a un método init en la clase MyNews y hágalo asíncrono.
class MyNews { ... async fetchNews() { await fetch(...); } ... async init() { await fetchNews(); } ... } const news = new MyNews(); await news.init(); news.showNews();Puede encontrar más información sobre los constructores asíncronos en este hilo de stackoverflow .
por cierto: el this.news; en el constructor parece innecesario.
Un par de problemas:
La primera es que fetchNews es un proceso asíncrono. En su ejemplo, showNews siempre será 0 porque fetchNews no ha obtenido nada cuando se llama showNews . Podemos mitigar algo de esto moviendo la llamada de esos métodos fuera de la clase.
Tiene una combinación extraña de fetch y async/await : es mejor quedarse con uno u otro.
class MyNews { apiUrl = 'https://jsonplaceholder.typicode.com/posts'; // `async/await` function - fetches the JSON, and // sets the `news` field to the parsed JSON async fetchNews() { try { const response = await fetch(this.apiUrl); if (response.ok) this.news = await response.json(); } catch (err) { this.error = err.message; } } formatNews() { return this.news.map(n => { return `<p>${n.title}</p>`; }).join(''); } // Return the news or the error if there was one. showNews() { if (!Array.isArray(this.news)) return this.error; return this.formatNews(); } } // Piecing it together we create a new // object, call and `await` `fetchNews` and // then call `showNews` async function main() { const myNews = new MyNews(); await myNews.fetchNews(); document.body.innerHTML = myNews.showNews(); } main(); p { border: 1px solid #787878; padding: 0.3em; border-radius: 5px; margin: 0.2em; text-transform: capitalize; } p:hover { cursor: pointer; background-color: #ffffd0; }