javascriptasync-awaitpromisepromise.all

I want to make 15 concurrent calls to an API using Promise.all( ) rather than successive calls. I can't figure out what I am doing wrong


Caveat: I'm learning to work with JavaScript Promises and API calls, so I try to write the code in promise.then format as well as async / await, so I know how to use both.

I have a container that I am initially filling with images from 15 successive API calls; each call returns a single image. As scrolling happens, more API calls are made. I am able to make everything work with straightforward Promises as well as async / await.

I am trying to rewrite the API calls to make them concurrent rather than successive. My new code doesn't work - depending on what I've done, I get no result (and no error in my console) or a typeError of undefined. I don't know what I am doing wrong.

This is the code that is working - in both formats (not including the page load code and intersectionObserver)

promises only

const getImage = function () {
  fetch("https://api.api-ninjas.com/v1/randomimage", {
    method: "GET",
    headers: {
      "X-API-Key": "###",
      Accept: "image/jpg", 
    },
  })
    .then((response) => {
      if (!response.ok) throw new Error("Network failure"); 
      return response.blob();
    })
    .then((blob) => {
      const img = document.createElement("img");
      img.src = URL.createObjectURL(blob);
      container.append(img);
    })
    .catch((err) => console.error(`${err.message}`));
};

async / await

const getImage = async function () {
  try {
    const response = await fetch("https://api.api-ninjas.com/v1/randomimage/", {
      method: "GET",
      headers: {
        "X-API-Key": "###",
        Accept: "image/jpg", 
      },
    });
    if (!response.ok)
      throw new Error(`Network failure! Status: ${response.status}`); 

    const data = await response.blob();
    const img = document.createElement("img");
    img.src = URL.createObjectURL(data);
    container.append(img);
  } catch {
    (err) => console.error(`${err.message}`);
  }
};

This is what I am trying for concurrent calls; I am calling the function after disabling all my other page load code just to see if it's working at all. Nothing loads on my page and I get no error or TypeError in my console.

const getImage = function () {
  fetch("https://api.api-ninjas.com/v1/randomimage", {
    method: "GET",
    headers: {
      "X-API-Key": "###",
      Accept: "image/jpg", 
    },
  });
};

const firstImages = async function () {
  try {
    const res = await Promise.all([
      getImage(),
      getImage(),
      getImage(),
      getImage(),
      getImage(),
      getImage(),
      getImage(),
      getImage(),
      getImage(),
      getImage(),
      getImage(),
      getImage(),
      getImage(),
      getImage(),
      getImage(),
    ]);
    const data = await Promise.all(res.forEach((r) => r.blob()));
    console.log(data);
    data.forEach((d) => {
      const img = document.createElement("img");
      img.src = URL.createObjectURL(d);
      container.append(img);
    });
  } catch {
    (err) => console.error(`${err.message}`);
  }
};

firstImages();

Solution

  • Let me try to help you out a bit. Also added a small loop so you can decide how many images you actually want to bring out. See comments in code as well:

    const getImage = function () {
      // Firstly, as Andy wrote in comments, return the fetch
      return fetch("https://api.api-ninjas.com/v1/randomimage", {
        method: "GET",
        headers: {
          "X-API-Key": "###",
          Accept: "image/jpg", 
        },
      });
    };
    
    const firstImages = async function (numberOfImages = 1) {
      try {
        const fetchArr = [];
    
        let i = 0;
        while (i <= numberOfImages) {
          fetchArr.push(getImage());
          i++;
        }
    
        const res = await Promise.all(fetchArr);
    
        // As was pointed out by another comment, use map here, as it will return an array that the Promise.all can iterate over.
        const data = await Promise.all(res.map((r) => r.blob()));
    
        data.forEach((d) => {
          const img = document.createElement("img");
          img.src = URL.createObjectURL(d);
          container.append(img);
        });
      } catch {
        // Probably best (as Keith commented) to just pass the error object to the console.error. It will print a few extra things that are normally noteworthy when tragedy strikes
        (err) => console.error(err);
      }
    };
    
    firstImages(15);
    

    Good luck!!