【问题标题】:promise chain is repeating itself after the call takes too long通话时间过长后,承诺链正在重复
【发布时间】:2018-08-20 18:28:01
【问题描述】:

我在 $http.get 调用中有一个很长的承诺链,需要几分钟才能完成。需要很长时间的部分是一个 for 循环,它遍历 160 个数组元素并运行一长串套接字连接测试。然而,在 for 循环的第 84 次迭代前后,整个 Promise 链(或者可能是 get 调用)重新开始。而另一个仍在运行。然后,一旦第一个完成, res.send 在新链运行时永远不会通过,这会无限重复。

router.get('/', function(req, res) {
 fs.readdir('C:\\Temp\\hostPorts', function(err, files) {
  console.log('files', files);
  chooseFile(files).then(response => {readTheFile(response).then(async (result) => {
    splitText(result).then( async (final) => {
      console.log('final version', final);
      res.send({file: final});
    })
    // res.send({file: result});
  }).catch(error => {
    console.log(error);
    }) //end catch
  }); //end promise
 }); //end read
}); //end get

这是我的调用,splitText 函数是它卡住的地方。我将在下面发布 splitText 函数的源代码,但我确信它以某种方式创建了两个实例,因为每次在第 84 次迭代时,我的终端控制台都会重新打印初始 console.log('files', files) 然后运行另一个链中的承诺。

它最终会完成第一个,因为 console.log('final version', final) 确实会打印出来。但是 res.send 永远不会发生,第二个承诺链继续运行。然后是第三个,等等。

这是来自长循环的代码

async function splitText(file){
  let tableData = "<table  border=1 cellspacing=0><tr>";

  let splited = file.trim().split(/\s+/);
        //vars for checking connectivity

        for (let i = 0; i < splited.length; i++) {
            console.log(splited[i] + " " + i);
            if(i < 4 ) {
                tableData += "<th>" + splited[i] + "</th>";
                //if its less than 4 print out table headers
            }
            else if (i == 4){
                tableData += "</tr><tr><td>" + splited[i] + "</td>";
                //if its 4 create a new row for data
            }
            else if (i % 3 == 0){
                //if modulo 3 is 0 then its on a port, checks connectivity and adds it to the row as data after port
                //then starts a new row
                let host = splited[(i - 1)]; //1 index ago was host
                let port = parseInt(splited[(i)]); //current array index is port
                console.log('host: ' + host );
                console.log('port: ' + port );
                await testPort(port, host).then(async (reachable) => {
                  console.log(reachable);
                  if (reachable) {
                    tableData += "<td>" + splited[i] + "</td><td>" + "<font color=\"GREEN\">UP</font>" + "</tr><tr>";
                   }
                  else {
              tableData += "<td>" + splited[i] + "</td><td>" + "<font color=\"RED\">DOWN</font>" + "</tr><tr>";
                    }
                });
              } //end else if
           else {
                tableData += "<td>" + splited[i] + "</td>";
                //otherwise adds tabledata
             }
        } //end for
     return tableData;
} //end function

这是检查主机/端口是否启动的异步函数。

async function testPort(port, host){
 return new Promise(resolve => {
    const socket = new net.Socket();

      const onError = () => {
        socket.destroy();
        resolve(false);
      };

      socket.setTimeout(10000);
      socket.on('error', onError);
      socket.on('timeout', onError);

      socket.connect(port, host, () => {
        socket.end();
     resolve(true);
   }); //end promise
 }); 

我不确定这是否是 HTTP 的问题。在花费太长时间后重新启动,但是我将超时设置为 5 分钟。或者,如果这是在没有得到响应后重新启动的承诺链。但我真的很想知道这一点,因为无论哪种方式,我都不会将数据返回给我的客户,而且据我所知,我从来没有回忆过一个函数或创建了一个无限循环。

【问题讨论】:

  • 添加更多日志以找出究竟是什么重复。
  • 你真的应该学习一下 Promise 是如何工作的,以及 awaitasync 的含义是什么。您的路由中没有正确链接 Promise,因此您无法记录这些 Promise 中发生的任何错误,并且 asnyc 没有用处。
  • 你能放一个你的主机端口文件的样本(至少像 10-15 列空间分隔符)吗?不清楚“i == 4”列(第一数据行中的第一列)如何处理第二列......
  • 其他由模 3 处理。直到第一行之后我才开始在那里处理它们,因为在与端口/主机相反的表头上运行我在 else 模 3 中的代码会返回错误

标签: javascript node.js promise async-await angular-http


【解决方案1】:

我不确定您使用的是什么框架,以及它是否有内部超时来处理重试请求。上述代码的问题之一是测试网络连接是否连续发生。当您有很多主机要检查时,它必然会失败/超时。您可以并行测试多个主机。为此,拆分、测试和构建输出的代码应该分开。这是一个初级版本。

function _splitText(file = '') {
  let ret = {
    header: [],
    hosts: {}
  };
  if (!file.length) {
    return ret;
  }
  //split the content
  let splitted = file.trim().split(/\s+/);
  if (splitted.length % 3 !== 1 && splitted.length < 4) {
    console.log('Invalid data!');
    return ret;
  }

  //get header
  ret.header = splitted.splice(0, 4);
  while (splitted.length) {
    const [name, host, port, ...rest] = splitted;
    ret.hosts[name] = {
      host,
      port,
      isReachable: false
    };
    splitted = rest;
  }
  return ret;
}
async function testPort(name, port, host) {
  return new Promise(resolve => {
    const socket = new net.Socket();

    const onError = () => {
      socket.destroy();
      resolve({
        name,
        isReachable: false
      });
    };

    socket.setTimeout(10000);
    socket.on('error', onError);
    socket.on('timeout', onError);

    socket.connect(port, host, () => {
      socket.end();
      resolve({
        name,
        isReachable: true
      });
    }); //end promise
  });
}
async function testPortsParallel(o, nParallel = 5, timeout = 10000) {
  const hostnames = Object.keys(o.hosts);
  let temp;
  while ((temp = hostnames.splice(0, nParallel))) {
    //create async promise for all hosts and wait for them at one go.
    await Promise.all(temp.map(v => testPort(v, o.hosts[v].host, o.hosts[v].port))).then(values => values.forEach(v => o.hosts[v.name].isReachable = v.isReachable));
  }
}

function buildOutput(o) {
  let ret = '<table  border=1 cellspacing=0>';
  //add header
  ret += '<tr><th>' + o.header.join('</th><th>') + '</th></tr>';
  //add hosts
  ret += o.hosts.keys().map(v => '<tr><td>' + [v, o.hosts[v].host, o.hosts[v].port].join('</td><td>') + '</td></tr>').join('');

  ret += '</table>'
}

async function splitText(s) {
  let data = _splitText(s);
  await testPortsParallel(data);
  return buildOutput(data);
}

splitText('name host port IsReachable 1 a b 2 c d');
//console.log(JSON.stringify(output));

希望这可能会有所帮助。您可以根据需要调整要并行测试的服务器数量。

注意:您的 testPort fn 中也有轻微的变化。

【讨论】:

    【解决方案2】:

    在异步语法上混合三种不同的风格并没有帮助自己。第一步真的应该是把代码简化成一个经常暴露问题的单一样式。

    乍一看,我怀疑您的问题是您如何链接承诺。您的一些 then 语句正在执行新的承诺,但没有返回承诺,这意味着您创建了多个承诺链。您也会丢失错误,因为它们没有 catch 子句。 我会重构为

    router.get('/', function(req, res) {
      fs.readdir('C:\\Temp\\hostPorts', function(err, files) {
        sendFile()
          .then(result => res.send(result));
          .catch(err => {
            console.error(err);
            res.sendError("Boom");
          }
      }
    } 
    
    async function sendFile() {
      const file= await chooseFile(files);
      const contents = await readTheFile(file);
      const splitContents = await splitText(contents);
      return {file: splitContents};
    }
    

    这使用了比标准承诺链更容易阅读的异步等待。您始终必须记住使用经典的 Promise 链从 then 子句中返回 Promise,否则您会遇到麻烦。

    【讨论】:

    • 感谢您抽出宝贵时间回复!我添加了您的更改以及一些额外的控制台日志,但我仍然遇到相同的问题,即在 for 循环的第 84 次迭代中,整个调用重新启动。感谢控制台日志,我确认它是重新触发的整个 get 调用,而不是 promise 链。我不知道是什么导致同一个 get 调用被双重触发
    • 对于初学者来说,自己编写一个函数测试。这将加快您的测试时间,并允许您验证它是否发生在这个地方。其次,异步函数应该返回值,而不是通过闭包修改值。您正在使用异步等待,因此如果您使用 .then 在异步函数中,您做错了什么。重写异步部分以返回值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-22
    • 2021-08-08
    • 2018-10-22
    • 2013-12-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多