-1

I know what is wrong with my code and I have looked into the best way of solving it, however with my lack of experience, I am having a hard time finding a good answer.

I need my first route(/data) to be fully completed before the second(/logo) express route sends the data. In short, I just need the variable symbolUrl to be completed before it goes into the second fetch call. Here is the code down below to explain

    app.use(express.static('public'));

    const url =
        'https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest';

    const qString =
         '?CMC_PRO_API_KEY=' + process.env.apiKey + '&start=1&limit=10&convert=USD';

    let symbol = [];
    
    app.get('/data', async (req, res) => {
      const fetch_res = await fetch(url + qString);
      const coinData = await fetch_res.json();
    
      for (let i = 0; i < 9; i++) {
        symbol.push(coinData.data[i]['symbol']);
      };
      res.json(coinData);
    });
      
    app.get('/logo', async (req, res) => {
      const symbolUrl = symbol.join(',');
      const url2 = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/info';
      const qString2 = `?CMC_PRO_API_KEY=${apiKey}%symbol=${symbolUrl}`;
      const fetch_res2 = await fetch(url2 + qString2);
      const coinLogo = await fetch_res2.json();
      res.json(coinLogo);
    });

The issue I am trying to solve with this project is that I want to send the data(/data) to be sent to the front end first because this API call will load the majority of the page. Then my second call will load images and other larger files afterward. HOWEVER, the API I am working with to get the logos(images) of the specific crypto coins I want, I need a different endpoint as well as use %symbol=${symbolUrl} in the API call to get the correct tokens I want to call.

client code:

fetch('http://localhost:2000/data')
  .then(async (response) => {
    return response.json();
  })
  .then(async (data) => {
    const parsedData = data['data'];
    // console.log(data['data'][0]['name'])

    await parsedData.forEach((element) => {
  // this just has about 20 lines of code generating the the look of the page. It works as intended 
});

 fetch('http://localhost:2000/logo')
    .then(async (response) => {
      return response.json();
    })
    .then(async (logo) => {
      console.log(logo)});
      

***I have tried putting this in an async function and awaiting the first fetch call

All I need to be done is for app.get(/data) to be fully complete before doing my second app.get. I have done testing and I know that is the issue. I apologize if it is something easy, but I couldn't find anything on making an app.get synchronous and I have tried putting both in a async function, however that did not work.

seth8656
  • 104
  • 5
  • 1
    So, your first route has no connection at all to your second route. These are completely separate requests as far as the server is concerned. If these are two requests coming from the same client and you want them sequenced there, then you need to have the client wait until `/data` is done before it sends the `/logo` request. That would be a client issue, not a server issue. – jfriend00 Jan 20 '22 at 03:39
  • 2
    But, the part where you are using the `symbol` variable looks wrong because that variable is shared by all requests to your server from all users. You can't just stuff data from one request into a server variable and use it some future request and not expect to have concurrency problems with multiple users using your server. – jfriend00 Jan 20 '22 at 03:40
  • 1
    Since this server architecture looks like it may be wrong, I'd suggest you back up several steps and explain what you're trying to accomplish here and the people here can maybe better suggest a more appropriate way to do it. Your question is asking about the about the wrong way to do whatever it is you're trying to do. – jfriend00 Jan 20 '22 at 03:41
  • added more information to the page like you said! Thank you so much! I took a course in just front-end stuff, so I have no experience with node. – seth8656 Jan 20 '22 at 12:24
  • This still appears to be a client problem. The CLIENT needs to wait for the `/data` request to be done BEFORE it sends the `/logo` request. Show your client code. – jfriend00 Jan 20 '22 at 14:38
  • I just posted the client code. I have tried putting everything in an async function and awaiting the first fetch but that didn't do it either. – seth8656 Jan 20 '22 at 16:27
  • Well, that client code does not send the `/logo` request until after the `/data` response has been received. So, I don't understand what you want help with at all. `await parsedData.forEach((element) => { ...});` is wrong since `.forEach()` has no return value so `await` is pointless there. If you have asynchronous operations inside that `.forEach()` that you thought you were awaiting, then perhaps there is a problem in that code you don't show. But, this code doesn't send the `/logo` request until after the `/data` response is received. – jfriend00 Jan 20 '22 at 21:38
  • Also, please don't go just putting `async` everywhere. You have it a whole bunch of places it is entirely unnecessary which makes me think you don't really understand how/when to use it. – jfriend00 Jan 20 '22 at 21:39

2 Answers2

1

You cannot send responses in fragments like you're trying to do, it would throw an error saying Can't set headers after they are sent to client

The proper method to implement what you are trying to do is to define the first layer as middleware, and then allow the second layer to return the response. Here layer basically means a function handler.

In order to control when the execution passes to the next layer / next function handler, express has a third parameter (request, response, next). You're only using request and response, researching about next will solve your concern. Express next function, what is it really for?

First handler

app.get('something_unique', async (req, res, next) =>  {
  // do whatever you want to do first
  // save data into res.locals
  res.locals.foo = {...}
  next()
})

Second Handler

app.get('something_unique', (req, res) => {
    const data = res.locals.foo;
    // whatever you want
    return res.json({ anything })
})

More:

  1. Express next function, what is it really for?
  2. Error: Can't set headers after they are sent to the client
  3. Passing variables to the next middleware using next() in Express.js
Yash Kumar Verma
  • 9,427
  • 2
  • 17
  • 28
  • How in the world would this ever work? These are two separate route handlers for two separate request paths. They do not both get called on the same request. Your idea could work if these were for the same path or middleware for all paths, but they're not and that is not what the OP is trying to do. So, as you show it how your `first` handler NEVER sends any response and since there are no other handlers for that path, it will eventually get to the default 404 handler. – jfriend00 Jan 20 '22 at 06:23
  • And, the OP won't get `Can't set headers after they are sent to client` because the two request handlers they show are not called on the same request - they're called on different requests. – jfriend00 Jan 20 '22 at 06:25
  • changed the url fragment to `something_unique`, thanks for pointing out – Yash Kumar Verma Jan 20 '22 at 08:59
  • @YashKumarVerma I tried using res.local however is said [Object: null prototype] {}. I also added a lot to this post so I am not sure if that changes the context of the problem. I will keep looking up .locals to see if that can actually save the variable. – seth8656 Jan 20 '22 at 19:36
  • i'm at work at the moment, will answer the question in ~12 hours – Yash Kumar Verma Jan 21 '22 at 04:01
0

I'm not sure what client code you're really running as it sounds like you've bee trying several things, but this should work to sequence the /data request and the /logo request so that the /logo request is not run until the response from the /data request has been received.:

async function run() {
    const r1 = await fetch('http://localhost:2000/data');
    const data = await r1.json();
    const parsedData = data.data;
    parsedData.forEach((element) => {
        // this just has about 20 lines of code generating 
        // the the look of the page. It works as intended
    });
    const r2 = await fetch('http://localhost:2000/logo');
    const logo = await r2.json();
    return logo;
}

run().then(logo => {
    console.log(logo);
}).catch(err => {
    // handle errors here
    console.log(err);
});

If there is any asynchronous code inside the .forEach(), then we will have to see that also to properly sequence that.


As I've said in my comments, stuffing the data from the first request into a server-side variable is probably the wrong design on the server because two separate clients both issuing /data requests will conflict with one another, creating race conditions. But, you haven't explained what this data is really for or why you're stuffing it into a variable on the server for us to suggest an alternate design.

jfriend00
  • 683,504
  • 96
  • 985
  • 979