Sorting Data Fetched Asynchronously with Javascript Fetch API

有些话、适合烂在心里 提交于 2019-12-11 17:38:49

问题


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)

回答1:


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); 



回答2:


I worked this solution with help from the webpage found here
However I am selecting the answer from @Bergi as the solution because the code is shorter, more elegant, and because Bergi raises the point that waiting for the fetch to complete diminishes the benefit of using streams.

All the working code for this problem can be found at this link under the function:
app.loadUsersListPage = async function(){Do stuff}

Notice the key word async above - That was necessary to make this work.

The action starts with a sub function of loadUsersListPage called onClickEventBehaviorOfSubmitQueryButton.

The following changes were made to that function in order to make this all work. Notice how it is different from the code in the original question.

    // Run the query defined in the textarea on the form.
    let recordsArray = await runQuery(document.querySelector(".queryExpressionTextArea").value)

    // 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);

The code above calls the code below which was also changed to make it all work.
Notice the use of the key words async and await throughout the function.
Please also notice at the bottom of the function where code has been changed so that data is not returned to the sort function until all the data has been received from the server.

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

          // Show the createCheck CTA
          document.getElementById("createNewRecordCTA").style.display = 'block';
          document.getElementById("createNewRecordCTA2").style.display = 'block';
          document.getElementById("createNewRecordCTA3").style.display = 'block';          

          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

    // Below the code has been changed so that data is not returned until the entire fetch has been completed.
    // Call to function which asks server for data.
    const res = await fetchPromise;

    const reader = res.getReader();

    let result = await reader.read();
    let fetchedArray = [];

    while (!result.done) {
      const value = result.value;
      fetchedArray.push(value);
      // get the next result
      result = await reader.read();
    }

    return fetchedArray;

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

Thanks to all for helping me work through this.
Clearly I need to study async/await and promises if I hope to accomplish my goals.



来源:https://stackoverflow.com/questions/58897018/sorting-data-fetched-asynchronously-with-javascript-fetch-api

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!