Search code examples
javascriptreactjsarrayssortingaxios

Why am I getting a random ordered array?


I'm currently trying to develop a dashboard application in React.js. I'm trying to fill an array of objects and map them in a list.

For every object I have to make an axios GET call to my server in order to get all the data. When I try to read the objects from the array I get a different order than I'm supposed to. The preferred output is that the objects in the array are sorted by DateTime.

My guess is that it has to do with threading and that the first completed request will be inserted before the others.

Does anyone know if there is a better way to what I'm doing now? Or if I'm forgetting something.

This is the main component where I perform my logic

const [shipments, setShipments] = useState([]);

    async function createRows(shipmentId){
      // The incoming id's are in correct order

      const details = await getShipmentDetails(shipmentId);
      // From this point on the responses are in a different order each time it's ran

      let row = {
        shipmentId: details.shipmentId,
        shipmentReference: details.shipmentReference,
        shipmentDateTime: details.shipmentDateTime,
        shipmentItems: details.shipmentItems,
      }
      
      if(shipments.map(s => s.shipmentId).indexOf(row.shipmentId) == -1){

        setShipments(rows => [...rows, row])
      }
    }

    async function retrieveShipments(){ 
      const amount = 5;
      await connectToBol();

      let allShipments = await getShipments();
      allShipments.splice(amount, (allShipments.length-amount + 1))

      if(allShipments.length > 0){
        allShipments.forEach(async shipment => { 
          await createRows(shipment.shipmentId);
        })
      }
    }


    useEffect(() => {
      retrieveShipments()
    }, []);

This is the method that performs the axios request

export async function getShipmentDetails(shipmentId){

    const response = await axios.get(`${baseUrl}/shipments/${shipmentId}`,{
        headers: {
            'Authorization': 'Bearer ' + <token>
        }
    })
        
    .catch((error) => {
        console.log('Error: ', error);
    });

    return response.data; 
}

Below is the output I'm currently getting enter image description here


Solution

  • I'm not going to focus on how to solve your problem, I'm going to focus on the question asked in the title if that's okay:

    Why am I getting a random ordered array?

    Well, here's the situation: you're using a forEach and making a bunch of api calls

        allShipments.forEach(async shipment => { 
          await createRows(shipment.shipmentId);
        })
    

    It may not be obvious, but just because you've used await in a .forEach loop does NOT mean that each item is processed in order - in fact, what's happening is all of the createRows calls are queued synchronously.

    If I do [1,2,3].forEach(v => someAsyncFunc(v));, it calls someAsyncFunc(1) and then someAsyncFunc(2) and then someAsyncFunc(3), but 2 and 3 are called without waiting for 1 to finish.

    So, if by some miracle someAsyncFunc(2) finishes before someAsyncFunc(1) does, you may end up with out-of-order results. That's what I believe is happening to you, and why you're asking this question.

    If you want someAsyncFunc(2) to run AFTER someAsyncFunc(1) finishes, you don't do a forEach loop, you do a for...of loop

    for (let x of [1,2,3]) {
        await someAsyncFunc(x);
    }
    

    Then, 2 will start only after 1 finishes.