Empty array returned when using fetch in a for loop

Currently, I am developing a server route to execute API calls.

I have encountered the need to make two separate fetch requests as I require additional information that is not available in the first fetch.

The issue lies in declaring a variable outside of the promise scope, causing my res.send function to not wait until the array is populated entirely.

My objective is to iterate until result 9 is achieved (predefined filters from TheDogApi cannot be used to display 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))
}

Your assistance on this matter would be greatly appreciated!

Answer №1

If you want to combine multiple fetch calls into a single promise that resolves when all responses are received, consider using Promise.all. This method will handle the aggregation of promises and ensure that all requests are completed successfully. To handle failures individually, you can use Promise.allSettled instead. Remember to catch any errors that occur during the process.

Always check the response.ok property before parsing the JSON data with .json(). It is essential to verify that the request was successful before proceeding. If the response is not okay, throwing an error and handling it in the .catch block is a common practice. Creating a wrapper function for fetch can also help reduce code verbosity.

Instead of using a traditional loop, consider using Array#slice to extract a subset of elements from an array. For arrays with fewer than 8 elements, this approach will work seamlessly without causing any issues.

// 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))
;

Answer №2

Here is a demonstration showcasing the use of an async function with await:

async function fetchData(queryName, key){
  const dataArray = [], response, jsonResponseArray = [];
  let firstResponse = await fetch(`https://api.thedogapi.com/v1/breeds/search?name=${req.query.name}&apikey=${key}`);
  let firstJson = await firstResponse.json(); // must be an Array
  for(let i=0, limit=8, jsonLength=firstJson.length; i<limit && i<jsonLength; i++){
    dataArray.push(fetch('https://api.thedogapi.com/v1/images/'+firstJson[i].reference_image_id));
  }
  response = await Promise.all(dataArray);
  for(let val of response){
    jsonResponseArray.push(val.json());
  }
  return Promise.all(jsonResponseArray);
}
// assumes req, req.query, req.query.name, and key are already defined
fetchData(req.query.name, key).then(data=>{
  // data is your JSON Array
});

Answer №3

Utilizing JSON Efficiently

My unique perspective: It's time to put an end to repeatedly using low-level functions like fetch whenever dealing with JSON data. Let's simplify the process by creating a single function, getJSON, that can be utilized throughout the codebase -

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

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

// ...

Elevating URL and URLSearchParams Handling

Here's another fresh idea: Say goodbye to manually constructing URLs and struggling with api access logic. By establishing a DogApi endpoint with a base url and an apikey, we can streamline our API interactions -

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

Now, accessing this endpoint is as simple as inputting the desired parameters -

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

// ...

The implementation of withApi is straightforward -

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
}

Paving the Way for Efficiency

With this approach, crafting functions like imagesForBreed becomes effortless, along with any other JSON-related or DogApi operations -

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)
}

Furthermore, your Express handler can now be condensed into a single line without unnecessary .then callbacks or extensive API configurations -

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using Vue.js, is it possible to apply a class to a clicked element and then remove it when another element is clicked?

While developing an app using Vue.js, I have run into an issue. I am familiar with how to solve this problem using jQuery, but I am struggling to find a solution within my Vue app. Let me explain quickly - I have a list of items and I want to assign a spec ...

What is the most effective way to loop through an object containing DOM selectors as values, and subsequently utilize them to assign new values?

I have a function that takes an object retrieved from a database as its argument. My goal is to display the values of this object in a form by associating each value with a specific DOM selector. Here is the code snippet where I achieve this: function pai ...

The React ternary operator within HTML does not display the correct HTML output

I'm currently learning React and facing a challenge with using a ternary operator. My goal is to display a minus sign by default, and then switch it to a plus sign when clicked. I implemented the ternary operator in my JSX and set the initial state of ...

The ajax signal indicates success, yet there seems to be no update in the database

Hey there, thank you for taking the time to read this. Below is the code I'm currently working with: scripts/complete_backorder.php <?php if(!isset($_GET['order_id'])) { exit(); } else { $db = new PDO("CONNECTION INFO"); ...

Creating a Robust Next.js Build with Tailor-Made Server (Nest.js)

I'm in need of assistance with compiling/building my project using Next.js while utilizing a custom server. Currently, I have integrated Nest.js (a TypeScript Node.js Framework) as the backend and nested Next.js within it. The integration seems to be ...

Shifting attention from the main point to the surrounding details (including siblings)

In my search bar layout, the main parent component consists of three child components - location dropdown, date picker (calendar), and duration dropdown. By default, the user's location is pre-selected in the location dropdown. I want the focus to shi ...

The extent of a nameless function when used as a parameter

I am currently developing a straightforward application. Whenever a user hovers over an item in the list (li), the text color changes to green, and reverts back to black when the mouse moves away. Is it possible to replace lis[i] with this keyword in the ...

A guide on extracting content from a PDF file with JavaScript

Hey there, I'm experimenting with various methods to extract content from a PDF file but so far nothing seems to be working for me. Even when I try using pdf.js, it shows an error and I can't figure out why. This is the approach I've been tr ...

Tips on bringing data from a php file into a javascript variable

I'm faced with a challenge where I need to extract data from a MySQL database using a PHP file and then store that data in a JavaScript array for plotting purposes with jQuery's flot library. Has anyone encountered a similar situation and found a ...

Is it possible to transfer data between two child components in Vue.js that share a common parent component?

In my project, I am working with 2 components that have a shared parent. My goal is to pass data from one child component to the other effectively. (I am specifically using vueJS 2 for this task) ...

Troubleshooting: Issues with jQuery.on method functionality

I'm currently using jQuery version 1.9.1 and I have a situation where I need to perform an action on a dynamically added td element. I attempted to utilize the jQuery.on function, however my code is not being triggered. Can someone please provide some ...

What is preventing my hidden field from being filled by a JavaScript function?

I've recently developed a JavaScript function that generates a specific string required for another form on the website I'm currently working on. Initially, I decided to store this generated value in a hidden field and then submit it within an HT ...

Switch or toggle between colors using CSS and JavaScript

Greetings for taking the time to review this query! I'm currently in the process of designing a login form specifically catered towards students and teachers One key feature I'm incorporating is a switch or toggle button that alternates between ...

The function is not recognized in C# programming language

Whenever I try to trigger functions by clicking buttons, nothing seems to happen and an error message appears in the console. Uncaught ReferenceError: AddressInputSet is not defined at HTMLButtonElement.onclick I'm new to this and could use ...

I am looking to retrieve a specific input value from a JSON array using JavaScript

I have created an array called 'PROPERTIES' which accepts values like username, password, sid, etc. I am looking to retrieve these entered values using JavaScript. 'PROPERTIES': {'gatewayurl': {'Name': ...

Arrange components within the form

Just started experimenting with this a few days ago. In my form, there is a text field and a button that I want to align side by side at the center of the form. .row { width: 100%; margin-bottom: 20px; display: flex; flex-wrap: wrap; } .col-6 { ...

Error injecting angular.bootstrap in Angular 1.6.5 version

I have a MeanJS.org skeleton app that I've transformed into hapi-js from express, switched to postgres from mongo, and incorporated OAUTH for authentication (mainly because I liked the server/client module folder structure - haha). Everything seems t ...

What is the best method for choosing elements when I already possess a DOM element?

When you have a variable that contains a DOM element and need to select related elements, you can easily achieve this by wrapping it in a jQuery object. var myDomElement = document.getElementById("foo"); $(myDomElement ).find("a"); It is common for peopl ...

The functionality of ngModel is not functioning properly on a modal page within Ionic version 6

Currently I am working on an Ionic/Angular application and I have encountered a situation where I am attempting to utilize ngModel. Essentially, I am trying to implement the following functionality within my app: <ion-list> <ion-item> <ion ...

Meteor: Incorporating New Fields when Creating an Account

Currently, I am experimenting with the Meteor Roles package found at https://github.com/alanning/meteor-roles in order to add a new field to the user model. The user creation process goes smoothly without any issues, however, the 'roles' field t ...