0

I'm writing a server route that makes api calls.

I need to make two different fetch requests cause I need more info that's coming in the first fetch.

The problem is that I'm declaring a variable out of the promise scope and for some reason, my res.send is not awaiting until the array gets full.

I need to iterate until result 9 (I can't use theDogApi's predefined filters to show nine results!)

if (req.query.name) {
    var myRes = [];
    fetch(`https://api.thedogapi.com/v1/breeds/search?name=${req.query.name}&apikey=${key}`)
        .then(r => r.json())
        .then( data => {

            for (let i = 0; i < 8 && i < data.length; i++) {
                fetch(`https://api.thedogapi.com/v1/images/${data[i].reference_image_id
                    }`)
                    .then(r => r.json())
                    .then(datos => {

                        myRes.push({ ...data[i], ...datos });
                    })
            }
        })
        .then(res.send(myRes))
}

I'll appreciate the help!

6
  • 1
    Use PromiseAll. Similar to this Commented Feb 18, 2021 at 22:53
  • 1
    Good time to use an async function with await, or some other Promise structure. Commented Feb 18, 2021 at 22:53
  • 1
    You also don't have a return statement in your second .then() and your third .then() will produce a syntax error. Commented Feb 18, 2021 at 22:54
  • @WeiYan that's helpfull but i don't really know how to implement it on my code.. Commented Feb 18, 2021 at 23:01
  • Reopening because this question is not really about returning a value from the function. it's more about managing loops of asynchronous operations and other asynchronous coding mistakes. Commented Feb 18, 2021 at 23:39

3 Answers 3

0

You can try using Promise.all to turn your array of fetch calls into an aggregate promise that resolves to an array of responses when all have arrived. If any fail, the whole thing fails (use Promise.allSettled if you don't want all-or-nothing semantics). Don't forget to catch the error.

Although the code doesn't show it, be sure to check response.ok to make sure the request actually succeeded before calling .json(). Throwing an error if !repsonse.ok and handling it in the .catch block is a typical strategy. Writing a wrapper on fetch is not a bad idea to avoid verbosity.

Lastly, note that Array#slice replaces the for loop. For arrays with fewer than 8 elements, it'll slice as many as are available without issue.

// mock everything
const fetch = (() => {
  const responses = [
    {
      json: async () => 
        [...Array(10)].map((e, i) => ({reference_image_id: i}))
    },
    ...Array(10)
      .fill()
      .map((_, i) => ({json: async () => i})),
  ];
  return async () => responses.shift();
})();
const req = {query: {name: "doberman"}};
const key = "foobar";
const res = {send: response => console.log(`sent ${response}`)};
// end mocks

fetch(`https://api.thedogapi.com/v1/breeds/search?name=${req.query.name}&apikey=${key}`)
  .then(response => response.json())
  .then(data => 
    Promise.all(data.slice(0, 8).map(e =>
      fetch(`https://api.thedogapi.com/v1/images/${e.reference_image_id}`)
        .then(response => response.json())
    ))
  )
  .then(results => res.send(results))
  .catch(err => console.error(err))
;

Sign up to request clarification or add additional context in comments.

Comments

0

Here is an example of an async function unsing await:

async function fun(queryName, key){
  const a = [], p, j = [];
  let firstWait = await fetch(`https://api.thedogapi.com/v1/breeds/search?name=${req.query.name}&apikey=${key}`);
  let firstJson = await firstWait.json(); // must be an Array
  for(let i=0,n=8,j,l=firstJson.length; i<n && i<l; i++){
    a.push(fetch('https://api.thedogapi.com/v1/images/'+firstJson[i].reference_image_id));
  }
  p = await Promise.all(a);
  for(let v of p){
    j.push(v.json());
  }
  return Promise.all(j);
}
// assumes req, req.query, req.query.name, and key are already defined
fun(req.query.name, key).then(a=>{
  // a is your JSON Array
});

2 Comments

that for loop is so hard on the eyes. have you seen for..of, or Array.prototype.map?
@Thankyou, of course I know about those loops. My second loop is a for..of loop. The for loop is very simple to read. ; separates initial_vars; condition; increment_vars. , separates initial_vars, and could separate increment_vars, as well. OP needed the loop to be less that 8. Yeah, I could have got rid of , j = [] up top, and done like const j = p.map(o=>o.json()); instead of the for..of loop, but I doubt it would increase performance, and that's easy to read.
0

JSON

Here's my hot take: Stop using low-level functions like fetch every time you want to get JSON. This tangles up fetching logic every time we want to get a bit of JSON. Write getJSON once and use it wherever you need JSON -

const getJSON = s =>
  fetch(s).then(r => r.json())

const data =
  await getJSON("https://path/to/some/data.json")

// ...

URL and URLSearchParams

Another hot take: Stop writing all of your URLs by hand. This tangles URL writing/rewriting with all of your api access logic. We can setup a DogApi endpoint once, with a base url and an apikey -

const DogApi =
  withApi("https://api.thedogapi.com/v1", {apikey: "0xdeadbeef"})

And now whenever we need to touch that endpoint, the base url and default params can be inserted for us -

const breed = 
  // https://api.thedogapi.com/v1/breeds/search?apikey=0xdeadbeef&name=chihuahua
  await getJSON(DogApi("/breeds/search", {name}))
    

// ...

withApi has a simple implementation -

const withApi = (base, defaults) => (pathname, params) =>
{ const u = new URL(url) // <- if you don't know it, learn URL
  u.pathname = pathname 
  setParams(u, defaults)
  setParams(u, params)
  return u.toString()
}

function setParams (url, params = {})
{ for (const [k,v] of Object.entries(params))
    url.searchParams.set(k, v)  // <- if you don't know it, learn URLSearchParams
  return url
}

fruits of your labor

Now it's dead simple to write functions like imagesForBreed, and any other functions that touch JSON or your DogApi -

async function imagesForBreed (name = "")
{ if (name == "")
    return []

  const breed = 
    await getJSON(DogApi("/breeds/search", {name}))

  const images =
    data.map(v => getJSON(DogAPI(`/images/${v.reference_image_id}`))
  
  return Promise.all(images)
}

And your entire Express handler is reduced to a single line, with no need to touch .then or other laborious API configuration -

async function fooHandler (req, res)
{ 
   res.send(imagesForBreed(req.query.name))
}    

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.