【问题标题】:Synchronous Requests in Node.jsNode.js 中的同步请求
【发布时间】:2022-02-13 22:21:39
【问题描述】:

如何使 Node.js 中的“请求”模块以同步方式加载内容?我见过的最好的建议是以某种方式使用回调来让函数在完成之前不返回自身。我正在尝试在代码中内联使用“请求”函数(需要根据不能放在回调中的数据进行处理)。

那么我如何使用“请求”模块的回调来防止它在加载资源完成之前返回自身?

我正在运行一个从 API 下载两个值的循环,然后必须根据这些值进行一些数学运算。虽然数学可以在回调中完成......循环将在没有执行下一个操作所需的值的情况下前进。 (因此在数据准备好之前停止循环前进将解决问题)

    /* loop */ {
         /* URL Generation */


    request( {url: base + u_ext}, function( err, res, body ) {
        var split1 = body.split("\n");
        var split2 = split1[1].split(", ");
        ucomp = split2[1];
    });

    request( {url: base + v_ext}, function( err, res, body ) {
        var split1 = body.split("\n");
        var split2 = split1[1].split(", ");
        vcomp = split2[1];
    });

    /* math which needs to be after functions get variables and before loop advances */
    }

【问题讨论】:

  • 你能显示一些代码吗?我相信任何同步的事情都可以以异步方式完成,但反之亦然。
  • 是的,有一些伪代码。
  • 这个问题是2年前的问题,这里有答案。

标签: javascript node.js


【解决方案1】:

简短的回答是:不要。 (...) 你真的不能。这是一件好事

我想就此澄清一下:

NodeJS 是否支持Synchronous Requests。它的设计目的不是开箱即用地支持它们,但如果你足够热心,有一些解决方法,这里有一个例子:

var request = require('sync-request'),
    res1, res2, ucomp, vcomp;

try {
    res1 = request('GET', base + u_ext);
    res2 = request('GET', base + v_ext);
    ucomp = res1.split('\n')[1].split(', ')[1];
    vcomp = res2.split('\n')[1].split(', ')[1];
    doSomething(ucomp, vcomp);

} catch (e) {}

当您在“同步请求”库上打开引擎盖时,您可以看到它在后台运行同步 child process。正如同步请求README 中所解释的那样,应该非常明智地使用它。这种方法会锁定主线程,这对性能不利。

但是,在某些情况下,编写异步解决方案几乎没有或根本没有优势(与编写更难阅读的代码所造成的某些危害相比)。

这是其他语言(Python、Java、C# 等)的许多 HTTP 请求库的默认假设,并且该理念也可以应用于 JavaScript。语言毕竟是解决问题的工具,如果利大于弊,有时您可能不想使用回调。

对于 JavaScript 纯粹主义者来说,这可能是异端,但我是一个实用主义者,所以我可以清楚地看到,如果您发现自己处于以下某些情况,使用同步请求的简单性会有所帮助:

  1. 测试自动化(测试通常本质上是同步的)。

  2. 快速 API 混搭(即黑客马拉松、概念验证等)。

  3. 帮助初学者的简单示例(之前和之后)。

请注意,上面的代码不应用于生产。如果您要运行适当的 API,请使用回调、使用 Promise、使用 async/await 或其他方式,但要避免使用同步代码,除非您希望在服务器上浪费大量 CPU 时间。

【讨论】:

  • 我知道这是非常陈旧的,但我不敢相信没有人为你的非常好的答案 +1。老实说,最让我生气的是节点开发人员(即使我是其中之一)是人们对如何/应该如何使用它的短视。同步执行是盒子里的一个工具(有时是一个重要的工具),就像其他任何东西一样。
  • 谢谢老兄!它是对一个老问题的一个相当新的答案,所以也许这就是为什么没有人看到它的原因。 ;)
  • 哦,如果我可以再补充一点……我真的不喜欢使用sync-request,因为它不采用标准的请求样式格式。这意味着处理表单编码帖子等的几个问题......看起来它只是为了与另一个节点服务器交谈,或者有非常具体的输入。例如,发送 JSON 编码数据不是我可以在后端轻松处理的事情。相反,我选择了 deasync,如下所示:stackoverflow.com/questions/9884418/…
  • 感谢上帝,我找到了这个答案。就我而言,我想在我的服务器崩溃之前调用另一台服务器。我根本无法在process.on('uncaughtException') 中异步执行任何操作,因为似乎事件循环不会等待它完成,所以我希望我的服务器在 HTTP 请求完成后崩溃并关闭。
  • 如果同步代码真的这么糟糕,他们早就把fs.readFileSync和朋友从node的fs库中删除了。这些同步方法使某些代码更简单,并且在适当的时候使用起来很有意义。让我们不要把所有的意识形态都放在人们身上!有时我需要代码坐下来等待结果,然后再继续。告诉提问者他们的 SO 问题无效只是一个糟糕的答案,应该被严重否决。难以置信。
【解决方案2】:

在 2018 年,您可以在 Node.js 中使用 asyncawait 编写“常规”样式。

下面是一个示例,它将请求回调包装在一个 Promise 中,然后使用await 来获取解析值。

const request = require('request');

// wrap a request in an promise
function downloadPage(url) {
    return new Promise((resolve, reject) => {
        request(url, (error, response, body) => {
            if (error) reject(error);
            if (response.statusCode != 200) {
                reject('Invalid status code <' + response.statusCode + '>');
            }
            resolve(body);
        });
    });
}

// now to program the "usual" way
// all you need to do is use async functions and await
// for functions returning promises
async function myBackEndLogic() {
    try {
        const html = await downloadPage('https://microsoft.com')
        console.log('SHOULD WORK:');
        console.log(html);

        // try downloading an invalid url
        await downloadPage('http://      .com')
    } catch (error) {
        console.error('ERROR:');
        console.error(error);
    }
}

// run your async function
myBackEndLogic();

【讨论】:

  • Javascript 多年来发生了很大变化,async/await 使代码更容易编写(并且嵌套更少)。感谢 Timo 的更新回复。
  • 但这并没有真正等待请求的响应或错误,然后它所属的函数才会继续......
  • 这不是真正回答问题,即如何使用node.js同步加载数据。
  • 因为它应该是 await myBackEndLogic(),但是 Node.js 很长时间不支持顶级等待,我认为即使是现在,在 2021 年,它们也有特殊的语义。
【解决方案3】:

虽然异步风格可能是 node.js 的本质,通常你不应该这样做,但有时你想这样做。

我正在编写一个方便的脚本来检查 API,并且不想用回调把它搞砸。

Javascript 无法执行同步请求,但 C 库可以。

https://github.com/dhruvbird/http-sync

【讨论】:

  • 最后 - 异步很棒,但回调地狱等待那些需要顺序请求的人,其 url 取决于最后一个响应。尝试异步发出数百个 HTTP 请求!谢谢,我会宣传这个。
  • 遗憾的是,这个库没有 npm install 与较新版本的 Node (14.15.5)。
【解决方案4】:

Aredridels 的回答相对较好(赞成),但我认为它缺乏循环等效项。这应该可以帮助您:

同步代码等效:

while (condition) {
  var data = request(url);
  <math here>
}
return result;

串行执行的异步代码:

function continueLoop() {
  if (!condition) return cb(result);
  request(url, function(err, res, body) {
    <math here>
    continueLoop()
  })
}
continueLoop()

【讨论】:

    【解决方案5】:

    你应该看看名为async的库

    并尝试使用 async.series 调用来解决您的问题。

    【讨论】:

      【解决方案6】:

      您可以使用retus 进行跨平台同步HTTP 请求。这是一个基于 sync-request 的库,但我添加了一些舒适的功能:

      const retus = require("retus");
      
      const { body } = retus("https://google.com");
      //=> "<!doctype html>..."
      

      就是这样!

      【讨论】:

      • retus 是同步请求的包装器,在 earlier answer 中提到过。此外,您应该披露自己是图书馆的作者。
      • 如您所愿。
      【解决方案7】:

      请参阅同步请求https://github.com/ForbesLindesay/sync-request

      例子:

      var request = require('sync-request');
      var res = request('GET', 'http://example.com');
      console.log(res.getBody());
      

      【讨论】:

      • 这已经被更详细地提到了in a previous answer
      • @DanDascalescu 谢谢!我更喜欢简洁(回答)
      • 我一直用来同时获得简洁和相关解释的好处的回答模式是引导解决方案(可能有一个非常简短的解释)和然后跟进详细解释。
      • 有道理!!!这是详细的解释:我想我明白你在说什么,这对我来说很有意义
      【解决方案8】:

      您可以使用请求库执行完全相同的操作,但这是使用 const https = require('https');const http = require('http'); 同步的,它应该随节点一起提供。

      这是一个例子,

      const https = require('https');
      
      const http_get1 = {
          host : 'www.googleapis.com',
          port : '443',
          path : '/youtube/v3/search?arg=1',
          method : 'GET',
          headers : {
              'Content-Type' : 'application/json'
          }
      };
      
      const http_get2 = {
      host : 'www.googleapis.com',
          port : '443',
          path : '/youtube/v3/search?arg=2',
          method : 'GET',
          headers : {
              'Content-Type' : 'application/json'
          }
      };
      
      let data1 = '';
      let data2 = '';
      
      function master() {
      
          if(!data1)
              return;
      
          if(!data2)
              return;
      
          console.log(data1);
          console.log(data2);
      
      }
      
      const req1 = https.request(http_get1, (res) => {
          console.log(res.headers);
      
          res.on('data', (chunk) => {
              data1 += chunk;
          });
      
          res.on('end', () => {
              console.log('done');
              master();
          });
      });
      
      
      const req2 = https.request(http_get2, (res) => {
          console.log(res.headers);
      
          res.on('data', (chunk) => {
              data2 += chunk;
          });
      
          res.on('end', () => {
              console.log('done');
              master();
          });
      });
      
      req1.end();
      req2.end();
      

      【讨论】:

        【解决方案9】:

        我为自己想出的最简单的解决方案是使用节点的本机“child_request”,并在内部使用简单的 curl 命令调用 exec....对于简单的罕见情况,所有依赖项和异步性都给我带来了太多麻烦我只是想“将http响应保存到节点中的变量”

        【讨论】:

          【解决方案10】:

          现在如果你想按顺序做事,你会做这样的事情:

          for (const task of tasks) {
            const response = await request(...);
          }
          

          这段代码(上面)按顺序运行请求,但不是同步的(意味着它不会阻塞主线程)。如果您真的需要同步,那么这些库中的大多数都可以归结为:

          const child = require('child_process');
          const buffer = child.execSync(`curl https://example.com/foo.json`);
          const data = JSON.parse(buffer.toString('utf8'));
          

          【讨论】:

            【解决方案11】:

            在撰写本文时,所有答案都是:

            1. 同步但使用控制流(例如使用异步库)
            2. 同步,但阻塞 [这意味着 Node 上的所有其他线程都已停止,这在性能方面很糟糕],例如 retus 或 sync-request。
            3. 过期,例如 http-request。

            查看基于deasync的非阻塞sync-request

            或者看这个帖子里的一些答案,比如this,直接使用deasync。

            【讨论】:

              【解决方案12】:

              简短的回答是:不要。如果您想要线性读取的代码,请使用 seq 之类的库。但不要指望同步。你真的不能。这是一件好事。

              几乎没有什么不能放在回调中。如果它们依赖于公共变量,请创建一个闭包来包含它们。手头的实际任务是什么?

              您希望有一个计数器,并且只在数据存在时调用回调:

              var waiting = 2;
              request( {url: base + u_ext}, function( err, res, body ) {
                  var split1 = body.split("\n");
                  var split2 = split1[1].split(", ");
                  ucomp = split2[1];
                  if(--waiting == 0) callback();
              });
              
              request( {url: base + v_ext}, function( err, res, body ) {
                  var split1 = body.split("\n");
                  var split2 = split1[1].split(", ");
                  vcomp = split2[1];
                  if(--waiting == 0) callback();
              });
              
              function callback() {
                  // do math here.
              }
              

              2018 年更新:node.js 在最近的版本中支持 async/await 关键字,并且使用将异步进程表示为 Promise 的库,您可以等待它们。您可以通过程序获得线性、顺序的流程,并且在您等待时其他工作可以进行。它构建得非常好,值得一试。

              【讨论】:

              • 运行一个必须从 API 下载两个不同值并对其进行操作的循环。数学可以在回调中完成......但是如果没有在回调中计算的数据,循环会继续前进,这会带来一个新问题。
              • 我最讨厌的是这样的答案,比如“简短的回答是:不要”
              • IMO 不是一个特别有用的答案...我正在验证用户输入,我需要根据 API 调用返回 true 或 false,同时 CLI 等待查看它是否可以继续。不确定这将如何从异步调用中受益,查找一个有效的问题并让人们教你他们认为你应该如何在概念上做某事而不是回答问题,这只是没有增加价值。
              • “不要”不是这个问题的解决方案。 F
              • 这个答案应该被删除,它不是在回答问题。
              猜你喜欢
              • 2018-10-31
              • 2018-02-14
              • 1970-01-01
              • 1970-01-01
              • 2014-12-27
              • 2015-02-10
              • 2017-04-13
              • 2016-05-20
              • 1970-01-01
              相关资源
              最近更新 更多