tarek hassan
tarek hassan

Reputation: 802

Async await is not waiting for response inside useEffect

I am creating simple clone for Hacker news, I want to use useEffect and inside it I return ids then use these ids to get first 50 posts.

The problem is with second function inside fetchHackerNewsPosts that it doesn't wait it to add data inside the postsArray and it prints it empty.

import React from "react";
import logo from "./logo.svg";
import "./App.css";

function App() {
  const [posts, setPosts] = React.useState([]);

  React.useEffect(() => {
    let postsArray = [];

    async function fetchHackerNewsPosts() {
      try {
        // it waits this function
        let postsIDsArray = await fetch(
          "https://hacker-news.firebaseio.com/v0/topstories.json"
        )
          .then((res) => res.json())
          .then((result) => {
            return result;
          });

        // it doesn't wait this function
        await postsIDsArray.slice(0, 50).map((postId) => {
          fetch(`https://hacker-news.firebaseio.com/v0/item/${postId}.json`)
            .then((res) => res.json())
            .then((result) => {
              postsArray.push(result);
            });
        });
      } catch (error) {
        console.log(error);
      }
    }

    fetchHackerNewsPosts();
    console.log(postsArray);
  }, []);

  return (
    <div className="p-10">
      <header className="">
        <h1> Hacker news API </h1>
      </header>
      <div className="bg-blue-600">list</div>
    </div>
  );
}

export default App;

Upvotes: 2

Views: 2533

Answers (1)

mbdavis
mbdavis

Reputation: 4010

I think there's two problems here.

1 - the function you're awaiting isn't an async function. You're doing post postsIDsArray.slice(0, 50).map(...) - which will just resolve to an empty array (you aren't returning the fetch). You could return the fetch, and then you'd have an array of promises, which you could then await via Promise.all:

await Promise.all(postsIDsArray.slice(0, 50).map((postId) => fetch(`https://hacker-news.firebaseio.com/v0/item/${postId}.json`)
  .then((res) => res.json())
  .then((result) => {
    postsArray.push(result);
 }));

This will still log an empty array where you console.log(postsArray); though - because you're not awaiting fetchHackerNewsPosts.

It might be worth considering using all await/async style or all promise style, rather than mixing the two. ~~This does however usually mean switching to regular loops rather than map/forEach, so you can wait in between.~~ - EDIT - removed, cheers Lione

React.useEffect(() => {
    async function fetchHackerNewsPosts() {
      const posts = [];

      try {
        const postIDsResponse = await fetch(
          "https://hacker-news.firebaseio.com/v0/topstories.json"
        );
        
        const postIDs = await postIDsResponse.json();

        for (const postID of postIDs) {
          const postResponse = await fetch(`https://hacker-news.firebaseio.com/v0/item/${postId}.json`);
          const post = await post.json();
          posts.push(post);
        }

        return posts;
      } catch (error) {
        console.log(error);
      }
    }

    setPosts(await fetchHackerNewsPosts());
  }, []);

As pointed out - Promise.all could actually be a lot quicker here as you don't need to wait for the previous request to finish before firing the next one.

React.useEffect(() => {
    async function fetchHackerNewsPosts() {
      try {
        const postIDsResponse = await fetch(
          "https://hacker-news.firebaseio.com/v0/topstories.json"
        );
        
        const postIDs = await postIDsResponse.json();

        const postRequests = postIDs.map(async postID => {
          const postResponse = await fetch(`https://hacker-news.firebaseio.com/v0/item/${postId}.json`);
          return await post.json();
        });
        
        return await Promise.all(postRequests);
      } catch (error) {
        console.log(error);
      }
    }

    const posts = await fetchHackerNewsPosts();
    setPosts(posts);
  }, []);

Upvotes: 5

Related Questions