skip to Main Content

Wrestling with promises here…

My users make a single request that requires multiple REST calls on the backend. I store each request made by the user and the two REST responses as an item in a ‘records’ array like so:

[
  {
    call:userRequest,
    responses: [
        {
          source:source1
          response:responseText
        },
        {
          source:source2
          response:responseText
        }
      ]
  }
]

I use a map function to make the mulitple REST API calls. I process each response using a ‘setThisCall’ method which adds details of each response to an array ‘thisCall’. Once done, ‘thisCall’ is used as The value for ‘responses’ in the ‘records’ array is assigned to ‘thisCall’.

await Promise.all(urls.map(async url =>  {
  
  fetch(url, { 
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: messageBody
  })
  .then((response) => response.json())
  .then((data) => {
    setThisCall(prev => ([ //sets thisCall
      ...prev,
      {
        source: source,
        response: data.body
      }
    ]))
  })
})).then(()=>{
  console.log("now adding to records")
  setRecords(prev => ([ //sets records
    {
      call: requestText,
      reponses: thisCall
      },
      ...prev
    ]))
  })

I tried using Promise.all() to let the map function complete the fecthes and processing the responses and using ‘then’ to add the calls to ne next ‘record’ item.

But the final ‘then’ is firing before ‘thisCall’ is built resulting in an empty ‘response’ value in ‘records’.

tl;dr – how do I make ‘setRecords’ happen AFTER the rest of the code has completed?

2

Answers


  1. It is preferable to use the async / await syntax.

    try {
      const responses = await Promise.all(urls.map(async (url) => {
        const response = await fetch(url, {
          method: "POST",
          headers: {
            "Content-Type": "application/json"
          },
          body: messageBody
        });
    
        return response.json();
      }));
    
      setThisCall((prev) => ([
        ...prev,
        ...responses.map((data) => ({
          source: source,
          response: data.body
        }))
      ]));
    
      console.log("이제 records에 추가합니다");
    
      setRecords((prev) => ([
        {
          call: requestText,
          responses: thisCall
        },
        ...prev
      ]));
    } catch (error) {
      console.error("Error:", error);
    }
    
    
    Login or Signup to reply.
  2. Issue

    Setter function of useState hook is async in nature.
    When setRecords is called, thisCall might not have been updated yet, that is why you’re seeing an empty response value in records.

    Solution

    Each fetch request returns a new promise that resolves after setThisCall is called. It will ensure that all the setThisCall are completed before setRecords.

    await Promise.all(urls.map(url =>  
      // Return a new promise that resolves after setThisCall
      new Promise((resolve, reject) => {
        fetch(url, { 
          method: "POST",
          headers: {
            "Content-Type": "application/json"
          },
          body: messageBody
        })
        .then(response => response.json())
        .then(data => {
          // Update thisCall with the response data
          setThisCall(prev => ([ 
            ...prev,
            {
              source: source,
              response: data.body
            }
          ]));
          // Resolve the promise after setThisCall
          resolve();
        })
        .catch(reject);
      })
    )).then(()=>{
      // Update records after all fetch requests have completed
      setRecords(prev => ([ 
        {
          call: requestText,
          responses: thisCall
        },
        ...prev
      ]))
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search