Search code examples
javascriptsortingasynchronousasync-awaitfetch-api

Sorting Data Fetched Asynchronously with Javascript Fetch API


The following javascript function called runQuery was given to me by @Brad
It gets data from a NodeJS server using the fetch API.
It works great! It returns data from the server.

Now I am trying to sort the data after all of it has been returned.
The following code runs without error but returns the data unsorted when viewed with the final console.log command.
That's because since runQuery is asynchronous, the sort function is working on an empty array so it does nothing. Then later, the console.log is showing the populated array (unsorted) after runQuery has had a chance to do it's work.

Can someone please help me understand how to sort the results only after all the data has been sent to the browser? Thanks, John

P.S. All working code for this project is shared here

// Run the query defined in the textarea on the form.
runQuery(document.querySelector(".queryExpressionTextArea").value).then(function()
{
  // Sort the recordsArray which was populated after running the query.
  recordsArray.sort(function(a, b)
  {
    //Sort by email
    if (a.email > b.email) return -1;
    if (a.email < b.email) return 1;
    if (a.email === b.email) return 0;
  })

  console.log(recordsArray); 
}); 
async function runQuery(queryExpression)
  {   
    // Define a client function that calls for data from the server.
    const fetchPromise = fetch('api/aUsers' + queryExpression)
    .then
    (
      (res) => 
      {
        // Verify that we have some sort of 2xx response that we can use
        if (!res.ok) 
        {
          // throw res;         

          console.log("Error trying to load the list of users: ");        
        }

        // If no content, immediately resolve, don't try to parse JSON
        if (res.status === 204) 
        {
          return;
        }

        // Initialize variable to hold chunks of data as they come across.
        let textBuffer = '';

        // Process the stream.
        return res.body

        // Decode as UTF-8 Text
        .pipeThrough
        (
          new TextDecoderStream()
        )

        // Split on lines
        .pipeThrough
        (
          new TransformStream
          (
            {
              transform(chunk, controller) 
              {
                textBuffer += chunk;            

                // Split the string of records on the new line character and store the result in an array named lines.
                const lines = textBuffer.split('\n');

                // Cycle through all elements in the array except for the last one which is only holding a new line character.
                for (const line of lines.slice(0, -1))
                {
                  // Put the element from the array into the controller que.
                  controller.enqueue(line);
                } // End of: for (const line ...)

                // Put the last element from the array (the new line character) into the textBuffer but don't put it in the que.
                textBuffer = lines.slice(-1)[0];             
              }, // End of: Transform(chunk, controller){do stuff}

              flush(controller) 
              {
                if (textBuffer) 
                {
                  controller.enqueue(textBuffer);
                } // End of: if (textBuffer)
              } // End of: flush(controller){do stuff}
            } // End of: parameters for new TransformStream
          ) // End of: call to constructor new TransformStream
        ) // End of: parameters for pipeThrough - Split on lines

        // Parse JSON objects
        .pipeThrough
        (
          new TransformStream
          (
            {
              transform(line, controller) 
              {
                if (line) 
                {
                  controller.enqueue
                  (
                    JSON.parse(line)
                  ); //End of: call to controller.enqueue function
                } // End of: if (line)
              } // End of: transform function
            } // End of: parameter object for new TransformStream
          ) // End of: new TransformStream parameters
        ); // End of: parameters for .pipeThrough - Parse JSON objects
      } // End of: .then callback function instruction for fetch
    ); // End of: .then callback parameters for fetch


    // Call to function which asks server for data.
    const res = await fetchPromise;

    const reader = res.getReader();

    function read() 
    {
      reader.read()
      .then
      (
        ({value, done}) => 
        {
          if (value) 
          {
            // Your record object (value) will be here.
            // This is a key/value pair for each field in the record.   
            //*************************
            // This array has global scope.
            // I want to sort this array only after all the data has been returned.  
            // In other words - only after this asynchronous function has finished running.   
            recordsArray.push(value);
            //*************************

            // If I were to uncomment the sort function in this position then
            // we will see the records sorted correctly in the final console.log.
            // I don't want to do this because then the sort function will
            // run every time a record is returned rather than one time after
            // all the records have been retrieved.

            //recordsArray.sort(function(a, b)
            //{
            //  //Sort by email
            //  if (a.email > b.email) return -1;
            //  if (a.email < b.email) return 1;
            //  if (a.email === b.email) return 0;
            //})


          } // End of: if(value){do stuff}

          if (done) {return;}

          read();

        } // End of: the actual anonymous callback arrow function.
      ); // End of: .then callback after read function completes.
    } // End of: function definition: function read(){do stuff}

    // Call the "read" function defined above when the submit query button is pressed.
    read()

  }; // End of: async function runQuery(queryExpression)

Solution

  • Looks like you're not looking for any streaming. Just write

    async function runQuery(queryExpression) {
        const res = await fetch('api/aUsers' + queryExpression);
        // Verify that we have some sort of 2xx response that we can use
        if (!res.ok) {
            console.log("Error trying to load the list of users: ");
            throw res;
        }
        // If no content, immediately resolve, don't try to parse JSON
        if (res.status === 204) {
            return [];
        }
        const content = await res.text();
        const lines = content.split("\n");
        return lines.map(line => JSON.parse(line));
    }
    

    and then

    const recordsArray = await runQuery(document.querySelector(".queryExpressionTextArea").value);
    recordsArray.sort(function(a, b) {
        return (a.email < b.email) - (a.email > b.email);
    })
    console.log(recordsArray);