Javascript for 循环不等待 .then 完成

Posted

技术标签:

【中文标题】Javascript for 循环不等待 .then 完成【英文标题】:Javascript for loop not waiting for .then to finish 【发布时间】:2022-01-04 01:27:24 【问题描述】:

我有以下代码可以从 Firebase 中检索一些文件信息:

function loadUserProfileKeys(key) 
  // Get the Firebase storage ref for the InitialUserProfiles folder
  var storageRef = firebase.storage().ref();
  var initialUserProfilesRef = storageRef.child('InitialUserProfiles'); // .txt files folder

  // Array
  const keyResults = [];

  // Retrieve all profiles
  initialUserProfilesRef.listAll()
    .then(function(res) 

      // Loop over each item
      for (const itemRef of res.items) 
        console.log("Start for loop");

        // Ignore profiles with symbols (workaround - TODO: fix this)
        if (/,|&/.test(itemRef.name))  else 
          // Path of the file
          var pathRef = initialUserProfilesRef.child(itemRef.name);

          // Get the file's download URL
          var downloadURL = pathRef.getDownloadURL()
            .then((url) => 

              // Get the given key from the user profile text file
              getValueKey(url, key)
                .then((value) => 
                  // Add it to the keyResults array
                  keyResults.push(value);
                );
            );
        
      
      console.log("End for loop");
      console.log(keyResults);
    ).catch((error) => 
      console.log("ERROR");
      console.log(error);
    );


async function getValueKey(fileURL, key) 
  let response = await fetch(fileURL);

  if (response.status == 200) 
    let json = await response.text(); // (3)
    var lines = json.split("\n");
    var results = [];
    for (var i = 0; i < lines.length; i++) 
      var line = lines[i];
      var pairs = line.split(":");
      if (pairs.length == 2 && pairs[0].trim() == key) 
        results.push(pairs[1].trim());
      
    
    return Promise.resolve(results[0]);
  

日志本身很好 - 在所有循环(即多个“开始循环”日志)完成之前,它不会记录“循环结束”。 问题是这仍然是 keyResults.push(value); 被调用之前 - 因此数组是空的(或者偶尔只是部分填充)。

如何让 var downloadURL = pathRef.getDownloadURL()getValueKey(url, key) 阻塞 - 以便在调用嵌套的 .then((value) 添加到数组之前它不会遍历循环?

我无法弄清楚异步等 - 我不断收到语法错误。


function loadUserProfileKeys(key) 
  // Get the Firebase storage ref for the InitialUserProfiles folder
  var storageRef = firebase.storage().ref();
  var initialUserProfilesRef = storageRef.child('InitialUserProfiles'); // .txt files folder

  const keyResults = Promise.all(initialUserProfilesRef.listAll().then(function(res) 
    // Loop over each item
    return res.items.map((itemRef) => 
      // Ignore profiles with symbols (workaround - TODO: fix this)
      if (/,|&/.test(itemRef.name))  else 
        // Path of the file
        var pathRef = initialUserProfilesRef.child(itemRef.name);

        // Get the file's download URL
        return pathRef.getDownloadURL()
          .then((url) => 
            // Get the given key from the user profile text file
            return getValueKey(url, key)
          );
      ;
    );
  ));
  console.log("End for loop");
  console.log(keyResults);


async function getValueKey(fileURL, key) 
  let response = await fetch(fileURL);

  if (response.status == 200) 
    let json = await response.text(); // (3)
    var lines = json.split("\n");
    var results = [];
    for (var i = 0; i < lines.length; i++) 
      var line = lines[i];
      var pairs = line.split(":");
      if (pairs.length == 2 && pairs[0].trim() == key) 
        results.push(pairs[1].trim());
      
    
    return Promise.resolve(results[0]);
  

【问题讨论】:

没有办法让 Promise 阻塞。你可能不得不使用回调。 我对 firebase 一无所知,但async function 可能会有所帮助。 【参考方案1】:

如果您想等待多个异步操作全部完成,您需要使用Promise.all()

这样的事情应该越来越近了:

return initialUserProfilesRef.listAll()
  .then((res) => 
    return Promise.all(
      res.items.map((itemRef) =>  // Loop over each item
        // Ignore profiles with symbols (workaround - TODO: fix this)
        if (/,|&/.test(itemRef.name)) 
          // you might want to return something here, e.g.
          return  skipped: true 
         else 
          // Path of the file
          var pathRef = initialUserProfilesRef.child(itemRef.name);

          // Get the file's download URL
          return pathRef.getDownloadURL()
            .then((url) => 
              // Get the given key from the user profile text file
              return getValueKey(url, key)
            );
        
      )
    );
  )
  .then((keyResults) => 
    console.log("End for loop");
    console.log(keyResults);
  )

【讨论】:

感谢您的帮助 :) 我已尝试使用您的建议来编辑问题。我现在收到Uncaught (in promise) TypeError: Argument of Promise.all is not iterable @SwiftBehemoth 这种方法最初将Promise.all() 交给Promise&lt;Promise[]&gt;,而不是Promise[] 本身,现在已经更正了。如果你制作loadUserProfileKeysasync,可以进一步简化。 感谢您修复 Sam! ? 我已经盯着它看了一段时间,但没有看到我的错误。 非常感谢你们! :)【参考方案2】:

我会将loadUserProfileKeys 设为异步函数。

这样您就可以简单地等待其他异步函数(pathRef.getDownloadURL()getValueKey(url, key))。

这是使用 async 和 await 修改的 Snipped。 我没有测试它,但它应该可以工作。

async function loadUserProfileKeys(key) 
  // Get the Firebase storage ref for the InitialUserProfiles folder
  var storageRef = firebase.storage().ref();
  var initialUserProfilesRef = storageRef.child('InitialUserProfiles'); // .txt files folder

  // Array
  const keyResults = [];

  try 
    // Retrieve all profiles
    const profileRes = await initialUserProfilesRef.listAll();

    // Loop over each item
    for (const itemRef of profileRes.items) 
      console.log("Start for loop");

      // Ignore profiles with symbols (workaround - TODO: fix this)
      if (/,|&/.test(itemRef.name))  else 
        // Path of the file
        var pathRef = initialUserProfilesRef.child(itemRef.name);

        // Get the file's download URL
        var downloadURL = await pathRef.getDownloadURL();

        // Get the given key from the user profile text file
        keyResults.push(await getValueKey(downloadURL, key));
      
    
    console.log("End for loop");
    console.log(keyResults);
   catch(error) 
    console.log("ERROR");
    console.log(error);
  

一般来说,我个人建议尽量避免封装 .then() 调用。

它只会让代码更难阅读和理解。

我发现 async & await 更干净。

【讨论】:

警告:虽然这种方法可行,但此答案会逐项处理每个项目。随着getDownloadURL() 解决问题的时间,应该使用并行方法。 @samthecodingman 这是真的。作为额外的优化,您可以用对项目的 .map() 调用替换 for 循环。那些 map 调用应该返回一个 Promise。并使用 Promise.all() 并行处理它们【参考方案3】:

你可以这样做

async function loadUserProfileKeys(key) 
    // Get the Firebase storage ref for the InitialUserProfiles folder
    var storageRef = firebase.storage().ref();
    var initialUserProfilesRef = storageRef.child('InitialUserProfiles'); // .txt files folder

    // Array
    const keyResults = [];

    // Retrieve all profiles
    const res = await initialUserProfilesRef.listAll();

    // Loop over each items
    res.items.forEach(async (itemRef) => 
        console.log("Start for loop");

        // Ignore profiles with symbols (workaround - TODO: fix this)
        if (/,|&/.test(itemRef.name)) 
            // skip
         else 
            // Path of the file
            const pathRef = initialUserProfilesRef.child(itemRef.name);

            // Get the file's download URL
            const url = await pathRef.getDownloadURL();
            
            // Get the given key from the user profile text file
            const value = await getValueKey(url, key);
            .
            // Add it to the keyResults array
            keyResults.push(value);
        
    );
    console.log("End for loop");
    console.log(keyResults);


async function getValueKey(fileURL, key) 
    let response = await fetch(fileURL);

    if (response.status == 200) 
        let json = await response.text(); // (3)
        var lines = json.split("\n");
        var results = [];
        for (var i = 0; i < lines.length; i++) 
            var line = lines[i];
            var pairs = line.split(":");
            if (pairs.length == 2 && pairs[0].trim() == key) 
                results.push(pairs[1].trim());
            
        
        return Promise.resolve(results[0]);
    

【讨论】:

警告:正如所写,此答案没有正确等待每个 Promise 在返回结果之前解决,因此不回答问题。 @samthecodingman 你确定吗?我不这么认为......它将等待每个承诺解决。在直接说它不起作用之前,您应该提供它会失败的原因。【参考方案4】:

所以补充一下 - 这可以按预期工作,但可能不是最佳的 + 可能会稍微整理一下:

async function loadUserProfileKeys(key) 
  // Get the Firebase storage ref for the InitialUserProfiles folder
  var storageRef = firebase.storage().ref();
  var initialUserProfilesRef = storageRef.child('InitialUserProfiles'); // .txt files folder

  // Results array
  var keyResults = [];

  // Retrieve all profiles
  const profiles = await initialUserProfilesRef.listAll()

  var promises = [];

  // Loop over each file
  for (const itemRef of profiles.items) 
    console.log("Start for loop");

    // Ignore profiles with symbols (workaround - TODO: fix this)
    if (/,|&/.test(itemRef.name))  else 
      // Path of the file
      var pathRef = initialUserProfilesRef.child(itemRef.name);

      // Add to the array of Promises
      promises.push(doSomething(pathRef, key));
    ;
  ;

  // Wait for all Promises to resolve
  keyResults = await Promise.all(promises)

  console.log("End for loop");
  console.log(keyResults);


async function doSomething(pathRef, key) 
  var downloadURL = await pathRef.getDownloadURL();
  var value = await getValueKey(downloadURL, key);
  return value


async function getValueKey(fileURL, key) 
  let response = await fetch(fileURL);

  if (response.status == 200) 
    let json = await response.text(); // (3)
    var lines = json.split("\n");
    var results = [];
    for (var i = 0; i < lines.length; i++) 
      var line = lines[i];
      var pairs = line.split(":");
      if (pairs.length == 2 && pairs[0].trim() == key) 
        results.push(pairs[1].trim());
      
    
    return results[0];
  

【讨论】:

以上是关于Javascript for 循环不等待 .then 完成的主要内容,如果未能解决你的问题,请参考以下文章

javascript:循环中如何等待方法完成了再继续?

在等待循环javascript期间访问当前迭代(索引)

使用javascript的Symbol.asyncIterator来等待循环

在 JavaScript 中,为啥“反向 while”循环比“for”快一个数量级?

将类添加到对象1,等待,在for循环中将类添加到对象2

Node.js 等待循环中读取的所有文件