2

I'm practicing my javascript skills doing a joke machine that collects jokes from an API and displays them, my problem is that currently, I'm using .then() which is fine, but I want to know how do I get my results in an array that doesn't have to wait for .then() like in fetchJokes().then(jokes => console.log(jokes));

I used to have a function that navigated the array like this:

let counter = 0;
let jokes = [joke1, joke2, joke3, joke4];

function nextJoke(jokes) {
  counter++;
  counter %= jokes.length; // start from 0 if we get to the end of the array
  return jokes[counter]; // give us back the actual item
}

That's how it was used before fetching from the API.

And this is my current code:

const url = "https://api.icndb.com/jokes/random/5";
const fetchButton = document.querySelector("button[type=button][value=Fetch]");

async function fetchJokes() {
  let jokes = [];
  let data = await (await fetch(url)).json();
  for (let i = 0; i < 5; i++) {
    jokes[i] = data.value[i].joke;
  }
  return jokes;
}

fetchButton.addEventListener("click", () => {
  fetchJokes().then(jokes => console.log(jokes));
});
<button type="button" value="Fetch">Fetch Jokes</button>

So I have figured out how to use buttons and all that jazz, just asking for help on how to pass the object that im receiving from fetchJokes() and store it in an Array.

ricardoNava
  • 685
  • 1
  • 7
  • 26
  • Well... you're already doing that, it seems. – Patrick Roberts Aug 25 '17 at 07:30
  • yes you are already returning an array from you async method. – Niladri Aug 25 '17 at 07:35
  • 2
    You cannot (should not) store it in a global array like before. What would you want the `nextJoke` function to do when it is called while the request is still loading? A) it has to wait itself - so store a promise for the array in the global variable B) it doesn't get called before the promise fulfills - so just declare it inside the `then` handler – Bergi Aug 25 '17 at 08:01
  • @Bergi What I'm trying to achieve is to fetch my `jokes` array and use it around my code without having to fetch it every time on every different function, read that you mention that I should not store it in a global array, so maybe my approach is wrong? Another example is if I try to `console.log(jokes)` outside of the `fetchJokes()` function. – ricardoNava Aug 25 '17 at 17:04
  • 2
    @Nava You don't have to fetch it every time, it's enough to wait for the same fetch everywhere - you can [store the promise itself in a global variable](https://stackoverflow.com/a/18745499/1048572). The alternative is to wrap all the code that wants to use `jokes` in a large `then` callback. – Bergi Aug 25 '17 at 22:17
  • @Bergi So basically `.then()` is just waiting for `fetchJokes()` to `return` and not actually calling the function right, and that's because the way promises work?, to what I understand from the post you mentioned, a best practice would be to have my array or whatever response I will work with in a closure(so I don't have it in the global scope)?. – ricardoNava Aug 26 '17 at 02:03
  • @ricardoNava `fetchJokes()` is called and immediately `return`s a promise on which the `.then()` method is invoked. Then later, the callback passed to `then` will get called when the promise fulfills. [No magic](https://stackoverflow.com/a/22562045/1048572) :-) Yes, having the asynchronous result not in the global scope is a best practice. – Bergi Aug 26 '17 at 12:37

3 Answers3

2

You can use fetch() to retrieve the url's data. Then you have to transform it to json with .then((resp) => resp.json()).

After that, you can store the data inside your jokes array.

const url = "https://api.icndb.com/jokes/random/5";
const fetchButton = document.querySelector("button[type=button][value=Fetch]");
const nextButton = document.querySelector("button[type=button][value=Next]");
const display = document.getElementById("display");
let counter = 0;
let jokes = [];

nextButton.style.visibility = 'hidden'; //Hide next button

fetchButton.addEventListener("click", () => {
  fetch(url).then((resp) => resp.json()) // Transform the data into json
    .then(data => (jokes = data.value, //Set the array of jokes
                  fetchButton.style.visibility = 'hidden', //Hide fetch button
                  nextButton.style.visibility = 'visible')); //Show next button
});

nextButton.addEventListener("click", () => {
  //Show next joke
  display.innerHTML = jokes[counter++ % jokes.length].id + " : " + jokes[counter++ % jokes.length].joke;
});
<button type="button" value="Fetch">Fetch Jokes</button>
<button type="button" value="Next">Next joke</button>
<p id="display"></p>
Weedoze
  • 13,683
  • 1
  • 33
  • 63
  • This actually does what I'm trying to achieve, but as some mentioned in other comments I think my doubts are with the global scope of `jokes`, or the lack of it. – ricardoNava Aug 25 '17 at 17:07
2

This is what worked for me without modifying the main purpose of the snippet in the question.

let counter = 0;
const url = "https://api.icndb.com/jokes/random/5";
const fetchButton = document.querySelector("button[type=button][value=Fetch]");

async function fetchJokes() {
  let jokes = [];
  let data = await (await fetch(url)).json();
  for (let i = 0; i < 5; i++) {
    jokes[i] = data.value[i].joke;
  }
  return jokes;
}

fetchButton.addEventListener("click", () => {
  fetchJokes().then(data => (jokes = data)).then(data => console.log(data));
});
<button type="button" value="Fetch">Fetch Jokes</button>
ricardoNava
  • 685
  • 1
  • 7
  • 26
0

I'm not sure I understand the question...If you want to fetch from an async api and not have to use .then or have to wait, then that is not possible.

From what I understand of the question, if you do not want to use .then every time, only fetch when the array is empty (only fetch once):

const url = "https://api.icndb.com/jokes/random/5";
const fetchButton = document.querySelector("button[type=button]
[value=Fetch]");
let arr = []
let counter = 0;
async function fetchJokes() {
    let data = await (await fetch(url)).json();
    for (let i = 0; i < 5; i++) {
        arr[i] = data.value[i].joke;
    }
}

function nextJoke(jokes) {
    const item = jokes[counter]
    counter++;
    if (counter >=jokes.length){
        counter %= jokes.length;
    }// start from 0 if we get to the end of the array
    return item; // give us back the actual item
 }
fetchButton.addEventListener("click", () => {
    if (arr.length == 0){
        fetchJokes()
        .then(()=>console.log(nextJoke(arr)))
    }else{
        console.log(nextJoke(arr))
    }
  });

DEMO codepen

There are many ways to do this. I would suggest looking more into Javascript Promises.

Francisco Flores
  • 290
  • 2
  • 16