RoksolanaVeres / Advice-generator_app

Advice Generator App / Frontend Mentor challenge
https://roksolanaveres.github.io/Advice-generator_app/
0 stars 0 forks source link

General thoughts and tips on fetching from the advice api #1

Closed arkatsy closed 1 year ago

arkatsy commented 1 year ago

Hey, I came through your frontendmentor post. I just prefer to leave the feedback in the actual code here in Github.

I'll try to be concise but also detailed about what I'm saying. I hope it helps!

The "problem" with the slip advice api is that they cache the advice for 2 seconds. Another solution to get around this issue is to ask on every api call for a specific advice by specifying the advice id.

You can do this by calling this: https://api.adviceslip.com/advice/177 where the 177 is the advice.

The total number of advices in the api ranges from 1 to 224. You can verify it yourself by going to https://api.adviceslip.com/advice/225 with a number higher than 224 where it will tell you Advice slip not found.

Anyway by doing this you just need to make sure you are calling on every click the api with a random number from 1 to 224

This requires a little bit of math. In case you're wondering how it works I'm leaving a stackoverflow link from where I took it.

const MIN_ADVICE_ID = 1;
const MAX_ADVICE_ID = 224;

/*
 *  Generates and returns a random integer between `MIN_ADVICE_ID` and `MAX_ADVICE_ID`
 *  For more: https://stackoverflow.com/questions/4959975/generate-random-number-between-two-numbers-in-javascript
 */
function generateRandomId() {
  return (
    Math.floor(Math.random() * (MAX_ADVICE_ID - MIN_ADVICE_ID + 1)) +
    MIN_ADVICE_ID
  );
}

Then every time you need to fetch a new advice you do it like this:

const ADVICE_BASE_API = "https://api.adviceslip.com/advice";

async function generateAdvice() {
  const id = generateRandomId(); // generate random id
  const res = await fetch(`${ADVICE_BASE_API}/{$id}`) // fetch the new advice with that id

  // return the advice (advice in this case is the returned object which contains the `id` and the `advice` as properties)
  const advice = await res.json();
  return advice.slip;
}

This will also solve another issue with Firefox. Basically in Firefox for some reason it doesn't generate a new advice even after 2 seconds. My guess is that this is probably a caching issue on the api's part. But doing it like this you basically get around this.


Also something fun that I just thought:

If you are using the id to generate advices you can try to cache the advice with the id so you don't make multiple api calls for advices you've already seen.

Nothing crazy, just a data structure (basically an object) like this:


// { "advice id": "advice text" }
{
    "1": "cool advice",
    "63": "Another cool advice",
    "132": "Another another cool advice",
    "77": "Another another another advice",
    "14": "You got the point"
}

You can do that with a Map object (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)

The good thing about Map is that it will guarantee for you that the keys (in this case the advice id) will be always unique.

// create a cache object
const cache = new Map();

// when you want to add a new entry in the cache:
// This goes after making a call to the api
cache.set(id, advice) // where the `id` is the id you generated and the `advice` is the advice you got by calling the api with that id.

// And now before you call the api to get the new advice, you can just check the cache if you already have the advice saved there
// from a previous call
cache.get(id) // if the id exists then it will return the advice. It's the same as using an object like this `cache[id]`.

That's all. Hope this was useful or at least interesting to read!

RoksolanaVeres commented 1 year ago

Thank you very much for your detailed feedback! I really have an issue with Firefox and did not have an idea how to solve it. Now, thanks to your advice I know in which direction to move

arkatsy commented 1 year ago

I'm glad it was helpful. About this Firefox issue now here is what I found.

The API as it says it does 2 second caching. The way it does caching is this:

You send the GET request to the server.

The server sends you the response with the the addition of this header: Cache-Control which is set to this: "cache-control": "max-age=2, max-age=600, private, must-revalidate"

Here is a screenshot on Chrome dev tools. These are the response headers.

image

You can find basically the same on Firefox. The server just sets this header before sending the request independent of the Browser.

I'm not sure I can provide a full correct explanation because myself have not got that deep into caching etc. But here is the main idea:

This header tells the browser to cache this response for X amount of time. In this X amount of period if the user tried to make another call to this endpoint do not make a call but instead use the cached one saved in the browser.

This X amount of time comes from the max-age directive (this is how they're usually calling these types of values). The max-age=2 basically means 2 seconds. The number is being interpreted as seconds.

To see it more visually you can run this in the dev tools of any browser.

async function getadvice() {
    const res = await fetch("https://api.adviceslip.com/advice");
    return res;
}

and then spam it a couple of times getadvice()

In Chrome dev tools, you'll see something like this in the network tab:

Notice how after the first request the next requests are saying (disk cache). This means that the request took the data from the cache instead of actually making a full trip to the server.

image

Now the question becomes why multiple max-age? Why at the same time max-age=2 and max-age=600. I don't know... I made some small research about it and I found this stackoverflow post if you are interested reading about it. For me it's too much time going that deep into it right now even though I'll keep it in my mind for future encounters with this issue.

https://stackoverflow.com/questions/25796967/multiple-max-age-cache-control-headers-in-response

But to put it simply this is the main takeaway: This header "cache-control": "max-age=2, max-age=600, private, must-revalidate" is being interpreted differently across the browsers.

Chrome takes the first max-age which is 2 (2 seconds). Firefox on the other hand uses the other max-age which is 600 (600 seconds).

This is my understanding of the issue. But anyway by calling the other endpoint with the id you just avoid all of this.

RoksolanaVeres commented 1 year ago

Thanks once again, your response is more than helpful🙂 I got a general idea and am going to try and implement it ASAP)

RoksolanaVeres commented 1 year ago

It really works! THANK YOU SO MUCH)))

arkatsy commented 1 year ago

Hell yeah! Now it's much much better!

Also thanks for responding. For some reason most people from the frontendmentor.io are not even responding to feedback or anything. Not that I care much about it since I have an excuse to exercise my technical writing skills but hearing back is always nice and welcome! I'm thinking of doing a couple challenges on frontendmentor because I really suck at writing good CSS and I want to improve at it. Feel free to reach out and leave some feedback if you want!

RoksolanaVeres commented 1 year ago

I always respond to feedbacks. After all, a person spent their time helping me to solve my issues, it's the least I can do in return.

Sure, I will occasionally check your solutions, but from what I see, you're more experienced than me, so I doubt that my responses will be as helpful as yours)