NodeJS 中的异步/等待

Posted

技术标签:

【中文标题】NodeJS 中的异步/等待【英文标题】:Async/Await In NodeJS 【发布时间】:2020-03-31 22:02:30 【问题描述】:

这在 NodeJS 中有效,但我必须在创建它的代码块中使用 recordObject。 即使在函数的开头声明了recordObject,它在代码块之外也是不可见的,因为函数在recordObject被检索之前继续。我想使用 async/await 来执行此操作,但在从 helpers.getMostRecent 返回后,我不知道在哪里获取 recordObject。

这是整个函数。 有人可以告诉我如何使用 Async/Await 执行此操作吗? 谢谢,约翰

// Define a function which builds a webpage and all supporting client/server code for 
// adding records to a json table in the database.
meta.build.AddWebpage = function(tableId)

  let dataObject = ;
  dataObject.uniqueField01Value = "";   
  dataObject.uniqueField01Name = "table.tableName";           
  dataObject.path = '/dbMetadata/metadata.json';
  dataObject.queryString = 'WHERE:;tableId:;MatchesExactly:;' + tableId + ':;';  

  // Collect information about the webpage from the metadata.
  // 1. Look in metadata.json - Read the object for the given tableId.
  helpers.getMostRecent(dataObject, function(errorFromGetMostRecent, payload)
  
    if(!errorFromGetMostRecent) // Got the most recent record from getMostRecent
    
      // Used to decode the payload buffer into readable text.
      let decoder = new StringDecoder('utf8');    

      // This instance of the Writable object gives us a place for a callback to run when the payload is received.
      const writable = new Writable();

      // Called by pipeline below. Does something useful with the payload
      writable.write = function(payload)     
      
        let stringContainer = '';                 
        stringContainer = stringContainer + decoder.write(payload);
        let recordObject = JSON.parse(stringContainer);

        // recordObject is all the metadata for the table.
        // I can use recordObject to build the webpage and all supporting code for adding records to a table
        ///////////////My problem is I have to do everything else in this code block////////////////////
        ?????????????????????????????How can I change this to Async/Await???????????????????????????????

      ; // End of: writable.write = function(payload)...

      // Passes the payload stream to the writable object which calls writable.write 
      // which does something useful with the payload.
      pipeline
      (
        payload,
        writable,
        function(error)if(error)console.log('There was an error.');
      );

     // End of: if(!error) Got the most recent record from gitHashedPass
    else // There was indeed an error returned by getMostRecent when attempting to get the most current record.
    
      helpers.log // Log the error.
      (
        7,
        'bxpa2p2t7ps3wrd1dqu7' + '\n' + 
        'The following was the error message from getMostRecent:' + '\n' +                                             
        errorFromGetMostRecent + '\n'                                                 
      ); // End of: helpers.log // Log the error.
     // End of: Else // There was indeed an error returned by getHashedPass when attempting to get the most current record.



  ); //End of: helpers.getMostRecent(dataObject, function(errorFromGetMostRecent, payload)

  // Assemble the webpage string from the metadata in recordObject

// End of: meta.build.AddWebpage = function(tableId)...

这是上面的函数调用的函数getMostRecent,以获取我们希望添加到会计系统的表的json记录。

// Define a function to retrieve the most current record in a table for a given primary key.
// Serves as table validation for put handlers (editing existing records)
// Checks that a record with the supplied primary key exists to modify.
// Also checks that a candidate values supplied for a unique fields are not already in the table. Enforces uniqueness.
// Streams the most current record back to the calling function.
helpers.getMostRecent = function(data, callback)

  // No value set but declared here because we need access to these thoughout the entire function.
  let queryArray, queryString;

  // typical example of a queryString: 'WHERE:;userId:;MatchesExactly:;' + userId + ':;'
  queryString = data.queryString

  // Make an array out of the queryString where each phrase of the query is an element.
  queryArray = queryString.split(":;");       

  // Create an empty map data structure which will be used to merge records with the same primary key. 
  // Hard if not impossible do that with objects.
  let tableMap = new Map();

  // Create a variable to track whether or not candidate values for any unique fields have already been used.
  let uniqueValuesAlreadyUsed = false;

  // Create a variable to track the primary key of a record that we may encounter which 
  // is holding a candidate unique value (perhaps and email address or phone number).
  // If we encounter this primary key again as we proceed through the records then we will 
  // check to see if the candidate unique value has been changed or if the record has been deleted. 
  // If so we will set this variable to false signifying that the candidate unique value is available again.
  let primaryKeyOfRecordHoldingCandidateUniqueValue = false

  // This function sets up a stream where each chunk of data is a complete line in the table file.
  let readInterface = readline.createInterface
  (
     // specify the file to be read.
      input: fs.createReadStream(helpers.baseDir + data.path)
    
  );

  // Look at each record in the file.
  readInterface.on('line', function(line) 
  
    // Convert the JSON string (a single line from the table file) into lineValueObject.
    let lineValueObject = JSON.parse(line);    

    // Declare a variable to serve as a key in the map to manage the lineValueObject.
    let primaryKey = lineValueObject[queryArray[1]];  

    let shouldDeleteThisRecord = false; 

    if(lineValueObject.deleted === true) // if the record in the table file had the delete field set to true:
    
      // If this record was holding our candidate unique field value:
      if(lineValueObject[queryArray[1]] === primaryKeyOfRecordHoldingCandidateUniqueValue) 
      
        // The record holding our candidate unique field value has been deleted so...

        // The candidate unique field value is available.
        uniqueValuesAlreadyUsed = false;

        // There is no more reason to track this record.
        primaryKeyOfRecordHoldingCandidateUniqueValue = false;          
      

      // This is not the record we are trying to modify.
      // Remove this record from the map. 
      shouldDeleteThisRecord = true;      
    
    else // The record was not deleted
     
      // If the current record does not have a primary key matching the record we wish to change:
      if(lineValueObject[queryArray[1]] != queryArray[3])
      
        // Check if this record has the same unique field value we wish to write. 
        // In other words: Has the unique field value already been used?
        if(lineValueObject[data.uniqueField01Name] === data.uniqueField01Value)
        
          // Currently this unique field value is taken. 
          // As we proceed, we may encounter a record with this same primary key where the unique field value has been changed.
          // Or, with this same primary key that has been deleted. 
          // Either case will make the unique field value available again as we proceed through the records
          // So we need to compare the primary key of this record to other records that we encounter.

          // Flag that the unique field value is already being used.
          uniqueValuesAlreadyUsed = true;

          // Take note of the primary key so that we can check as we proceed if this record gets 
          // deleted or if the unique field value gets changed.
          primaryKeyOfRecordHoldingCandidateUniqueValue = lineValueObject[queryArray[3]];
         // End of: Check if this record has the same unique field value we wish to write. Is the unique field value already taken?

        // Well then - Not deleted, not the same key as the record we want to change, not the candidate unique field value so...
        // Check if this record was previously holding the candidate unique field value but is now changed.
        else if
        (
          primaryKeyOfRecordHoldingCandidateUniqueValue === lineValueObject[queryArray[1]]
          &&
          lineValueObject[data.uniqueField01Name] != data.uniqueField01Value 
        )
        
          // This record was tying up the candidate unique field value but is no longer holding it.
          // The candidate unique field value is available again.
          uniqueValuesAlreadyUsed = false;

          // There is no more reason to track this record.
          primaryKeyOfRecordHoldingCandidateUniqueValue = false;           
        

        // This is not the record we are trying to modify.
        // Remove this record from the map.
        shouldDeleteThisRecord = true;
       // End of: If the current record does not have a primary key matching the record we wish to change:

     //End of: else - The record was not deleted

    // If the record was not marked for deletion and has the primary key of the record we wish to change:
    // This is the record we are going to send back to the calling function.
    if(shouldDeleteThisRecord == false)
              
      // Update this record in the map.
      tableMap.set(primaryKey, lineValueObject);
    
    else // shouldDeleteThisRecord is true. This is not the record we are trying to modify. Don't send it back.
    
      tableMap.delete(primaryKey);
    

  ); // End of: readInterface.on('line', function(line)...
  // End of: Look at each record...


  // This listener fires after we have looked through all the records in the table file.
  // The callback function defined here will stream one record back to the clients browser.
  readInterface.on('close', function() 
            
    // This readable stream will be used to write the result of the merge to a new file.
    const sourceStream = new Readable(); 

    // Check that the specified record was found.
    if(tableMap.size === 0)
    
      helpers.log
      (
        4,
        'xwmv16fc90bzrbnvhbg5' + '\n' +
        'No record found for primary key' + '\n'
      );
            //params:  error 
      return callback("No record found for primary key");
    

    if(uniqueValuesAlreadyUsed === true)
    
      helpers.log
      (
        4,
        'xwmv16fc90bzrbnvhbg5' + '\n' +
        'This ' +  data.uniqueField01Name + ' value already exists' + '\n'
      );
            //params:  error 
      return callback('This ' +  data.uniqueField01Name + ' value already exists');
    

    for (const [key, valueObject] of tableMap)
    
      // Convert the data object to a string.
      let stringData = JSON.stringify(valueObject);     

      // Load the readable stream with data.
      sourceStream.push(stringData + '\n');                  
                    

    // Tell the stream no more data is coming.
    sourceStream.push(null);     

    //params:  error, json record
    callback(false, sourceStream);             

  ); // End of: readInterface.on('close', function()...   

; // End of: helpers.getMostRecent = function(data, callback)...
// End of: // Define a function to retrieve the most current record in a table for a given primary key.

【问题讨论】:

【参考方案1】:

我建议阅读https://alligator.io/js/async-functions/ 或类似教程,以更好地理解async/await。有了这个,您可能会重写您的函数并使您的流程更易于使用。

使用async/await 将回调函数重写为函数的简单示例如下所示:

没有async/await

function consumerFunction() 
  myFunction(1, function(error, value) 
    console.log(error, value); // outputs: null, 2
  );


function myFunction(value, callback) 
  setTimeout(() =>  // mimick async behaviour
    callback(null, value + 1);
  , 1000);

async/await:

async function consumerFunction() 
  try 
    const value = myFunction(1);
    console.log(value); // outputs 2
   catch (error) 
    // Code to handle potential errors
  


function myFunction(value) 
  return new Promise(function (resolve, reject) 
    setTimeout(() =>  // mimick async behavoir
      resolve(value + 1); // success case
      // reject('Something bad happened'); // error case
    , 1000);
  )

【讨论】:

谢谢@tomahaug。通过您的示例开始看到它。我已经在使用 async/await 将流从 NodeJS 传递到带有 fetch 的浏览器。现在我从您的示例中看到,必须有一个承诺,而我的获取就是承诺。现在我想我只需要弄清楚上面代码中的哪些内容需要包含在一个 Promise 中,以及最终的 await 需要去哪里。感谢您为我指明正确的方向。 听起来您走在正确的道路上。祝你好运!

以上是关于NodeJS 中的异步/等待的主要内容,如果未能解决你的问题,请参考以下文章

NodeJS Express 异步/等待

NodeJS,从异步等待中获取返回值

等待似乎不起作用,异步(nodejs)

NodeJs - 控制器内的异步/等待[重复]

需要帮助在 NodeJS 中使用 Sinon 存根异步 / 等待承诺

NodeJS,如何强制异步 for 循环在传递到下一次迭代之前等待 HTTP 请求解决,这样我们就不会收到 EMFILE 错误?