【问题标题】:Make Requests in Sequential Order Node.js按顺序发出请求 Node.js
【发布时间】:2011-08-28 06:09:38
【问题描述】:

如果我需要按顺序调用 3 个 http API,下面的代码有什么更好的替代方法:

http.get({ host: 'www.example.com', path: '/api_1.php' }, function(res) { 
  res.on('data', function(d) { 

    http.get({ host: 'www.example.com', path: '/api_2.php' }, function(res) { 
      res.on('data', function(d) { 

        http.get({ host: 'www.example.com', path: '/api_3.php' }, function(res) { 
          res.on('data', function(d) { 


          });
        });
        }
      });
    });
    }
  });
});
}

【问题讨论】:

  • 除了清理它,我不认为你能做得比这更好。
  • 为什么它们需要按顺序排列?
  • @Raynos 你可能需要一些来自 api_1 的数据才能知道要发送什么到 api_2
  • 值得一提的是,Futures 已被弃用,请考虑使用更新的库,如 Bluebird 或 Q。
  • 标题和问题相互矛盾。您在问题中描述的不是同步请求,而是一系列请求,通常每个请求都是异步发生的。很大的区别——一个同步调用阻塞,而异步操作序列不阻塞(阻塞UI,阻塞服务器处理其他请求)。下面有一个答案提到了sync-request 库,这是对这个问题的标题的一个很好的回答,但不是问题代码所暗示的答案。下面关于 Promises 的答案是一个更好的答案。你的意思是什么?

标签: node.js synchronization


【解决方案1】:

我会使用带有 api 列表的递归函数

var APIs = [ '/api_1.php', '/api_2.php', '/api_3.php' ];
var host = 'www.example.com';

function callAPIs ( host, APIs ) {
  var API = APIs.shift();
  http.get({ host: host, path: API }, function(res) { 
    var body = '';
    res.on('data', function (d) {
      body += d; 
    });
    res.on('end', function () {
      if( APIs.length ) {
        callAPIs ( host, APIs );
      }
    });
  });
}

callAPIs( host, APIs );

编辑:请求版本

var request = require('request');
var APIs = [ '/api_1.php', '/api_2.php', '/api_3.php' ];
var host = 'www.example.com';
var APIs = APIs.map(function (api) {
  return 'http://' + host + api;
});

function callAPIs ( host, APIs ) {
  var API = APIs.shift();
  request(API, function(err, res, body) { 
    if( APIs.length ) {
      callAPIs ( host, APIs );
    }
  });
}

callAPIs( host, APIs );

编辑:请求/异步版本

var request = require('request');
var async = require('async');
var APIs = [ '/api_1.php', '/api_2.php', '/api_3.php' ];
var host = 'www.example.com';
var APIs = APIs.map(function (api) {
  return 'http://' + host + api;
});

async.eachSeries(function (API, cb) {
  request(API, function (err, res, body) {
    cb(err);
  });
}, function (err) {
  //called when all done, or error occurs
});

【讨论】:

  • 这是我采用的方法,因为我有一个可变的请求列表(600 个项目并且还在增长)。也就是说,您的代码存在问题:如果 API 输出大于块大小,则每个请求将多次发出“数据”事件。您想像这样“缓冲”数据: var body = ''; res.on('data',function(data){ body += data; }).on('end',function(){ callback(body); if (APIs.length) callAPIs(host, APIs);} );
  • 已更新。我只是想展示如何通过递归使问题变得更简单/更灵活。就我个人而言,我总是将 request 模块用于此类事情,因为它可以让您轻松跳过多个回调。
  • @generalhenry,如果我想使用请求模块,我该怎么做?能否提供一个使用request实现上述的代码sn-p?
  • 我添加了一个请求版本和一个请求/异步版本。
【解决方案2】:

使用 Futures 之类的延迟。

var sequence = Futures.sequence();

sequence
  .then(function(next) {
     http.get({}, next);
  })
  .then(function(next, res) {
     res.on("data", next);
  })
  .then(function(next, d) {
     http.get({}, next);
  })
  .then(function(next, res) {
    ...
  })

如果你需要传递作用域,那么就做这样的事情

  .then(function(next, d) {
    http.get({}, function(res) {
      next(res, d);
    });
  })
  .then(function(next, res, d) { })
    ...
  })

【讨论】:

  • 请尝试IcedCoffeScript,它为nodejs提供等待和延迟。
  • 这是非阻塞的吗?我的意思是它会阻塞下一个函数,但这不会阻塞其他异步函数的执行,对吗?
  • 是的,延迟方法是非阻塞/异步的。
  • ES6 Promise API 应该有效地替换它,即使根据“Futures”的作者
  • Futures 非常陈旧且已被弃用。请参阅 q。
【解决方案3】:

使用request 库可以帮助减少麻烦:

var request = require('request')

request({ uri: 'http://api.com/1' }, function(err, response, body){
    // use body
    request({ uri: 'http://api.com/2' }, function(err, response, body){
        // use body
        request({ uri: 'http://api.com/3' }, function(err, response, body){
            // use body
        })
    })
})

但为了获得最大的震撼,您应该尝试一些控制流库,例如 Step - 它还允许您并行化请求,假设它是可以接受的:

var request = require('request')
var Step    = require('step')

// request returns body as 3rd argument
// we have to move it so it works with Step :(
request.getBody = function(o, cb){
    request(o, function(err, resp, body){
        cb(err, body)
    })
}

Step(
    function getData(){
        request.getBody({ uri: 'http://api.com/?method=1' }, this.parallel())
        request.getBody({ uri: 'http://api.com/?method=2' }, this.parallel())
        request.getBody({ uri: 'http://api.com/?method=3' }, this.parallel())
    },
    function doStuff(err, r1, r2, r3){
        console.log(r1,r2,r3)
    }
)

【讨论】:

    【解决方案4】:

    我也喜欢 Raynos 的解决方案,但我更喜欢不同的流控制库。

    https://github.com/caolan/async

    根据您是否需要每个后续函数中的结果,我会使用系列、并行或瀑布。

    Series 当它们必须被串行执行时,但您不一定需要每个后续函数调用中的结果。

    Parallel如果它们可以并行执行,则在每个并行函数期间不需要每个结果,并且需要在所有完成后回调。

    Waterfall 如果你想在每个函数中变形结果并传递给下一个

    endpoints = 
     [{ host: 'www.example.com', path: '/api_1.php' },
      { host: 'www.example.com', path: '/api_2.php' },
      { host: 'www.example.com', path: '/api_3.php' }];
    
    async.mapSeries(endpoints, http.get, function(results){
        // Array of results
    });
    

    【讨论】:

    • var http = require('http');
    • 哈。 example.com 实际上是为这类事情设计的域。哇。
    • async.series 代码不起作用,至少在 async v0.2.10 中是这样。 series() 最多只接受两个参数,并将第一个参数的元素作为函数执行,因此 async 尝试将对象作为函数执行时会抛出错误。
    • 您可以使用 forEachAsync (github.com/FuturesJS/forEachAsync) 执行与此代码的预期类似的操作。
    • 这正是我想要的。谢谢!
    【解决方案5】:

    有很多控制流库——我喜欢conseq(...因为我写了它。)另外,on('data') 可以触发多次,所以使用像restler 这样的 REST 包装库。

    Seq()
      .seq(function () {
        rest.get('http://www.example.com/api_1.php').on('complete', this.next);
      })
      .seq(function (d1) {
        this.d1 = d1;
        rest.get('http://www.example.com/api_2.php').on('complete', this.next);
      })
      .seq(function (d2) {
        this.d2 = d2;
        rest.get('http://www.example.com/api_3.php').on('complete', this.next);
      })
      .seq(function (d3) {
        // use this.d1, this.d2, d3
      })
    

    【讨论】:

      【解决方案6】:

      似乎这个问题的解决方案永无止境,这里还有一个:)

      // do it once.
      sync(fs, 'readFile')
      
      // now use it anywhere in both sync or async ways.
      var data = fs.readFile(__filename, 'utf8')
      

      http://alexeypetrushin.github.com/synchronize

      【讨论】:

      • 尽管您链接的库确实为 OP 的问题提供了解决方案,但在您的示例中,fs.readFile 始终是同步的。
      • 不,您可以显式提供回调并将其用作异步版本。
      • 这个例子是用于 http 请求,而不是文件系统通信。
      【解决方案7】:

      您可以使用我的Common Node library

      function get(url) {
        return new (require('httpclient').HttpClient)({
          method: 'GET',
            url: url
          }).finish().body.read().decodeToString();
      }
      
      var a = get('www.example.com/api_1.php'), 
          b = get('www.example.com/api_2.php'),
          c = get('www.example.com/api_3.php');
      

      【讨论】:

      • 废话,我赞成认为它会起作用,但它不起作用:(require(...).HttpClient is not a constructor
      【解决方案8】:

      另一种可能性是设置一个回调来跟踪已完成的任务:

      function onApiResults(requestId, response, results) {
          requestsCompleted |= requestId;
      
          switch(requestId) {
              case REQUEST_API1:
                  ...
                  [Call API2]
                  break;
              case REQUEST_API2:
                  ...
                  [Call API3]
                  break;
              case REQUEST_API3:
                  ...
                  break;
          }
      
          if(requestId == requestsNeeded)
              response.end();
      }
      

      然后只需为每个任务分配一个 ID,您就可以设置在关闭连接之前必须完成哪些任务的要求。

      const var REQUEST_API1 = 0x01;
      const var REQUEST_API2 = 0x02;
      const var REQUEST_API3 = 0x03;
      const var requestsNeeded = REQUEST_API1 | REQUEST_API2 | REQUEST_API3;
      

      好吧,它不漂亮。这只是进行顺序调用的另一种方式。可惜NodeJS没有提供最基本的同步调用。但我明白异步的诱惑是什么。

      【讨论】:

        【解决方案9】:

        使用顺序。

        sudo npm install sequenty

        https://github.com/AndyShin/sequenty

        非常简单。

        var sequenty = require('sequenty'); 
        
        function f1(cb) // cb: callback by sequenty
        {
          console.log("I'm f1");
          cb(); // please call this after finshed
        }
        
        function f2(cb)
        {
          console.log("I'm f2");
          cb();
        }
        
        sequenty.run([f1, f2]);
        

        你也可以使用这样的循环:

        var f = [];
        var queries = [ "select .. blah blah", "update blah blah", ...];
        
        for (var i = 0; i < queries.length; i++)
        {
          f[i] = function(cb, funcIndex) // sequenty gives you cb and funcIndex
          {
            db.query(queries[funcIndex], function(err, info)
            {
               cb(); // must be called
            });
          }
        }
        
        sequenty.run(f); // fire!
        

        【讨论】:

          【解决方案10】:

          这是我的@andy-shin 版本,其参数在数组而不是索引中:

          function run(funcs, args) {
              var i = 0;
              var recursive = function() {
                  funcs[i](function() {
                      i++;
                      if (i < funcs.length)
                          recursive();
                  }, args[i]);
              };
              recursive();
          }
          

          【讨论】:

            【解决方案11】:

            sync-request

            到目前为止,我发现和使用的最简单的一个是sync-request,它同时支持节点和浏览器!

            var request = require('sync-request');
            var res = request('GET', 'http://google.com');
            console.log(res.body.toString('utf-8'));
            

            就是这样,没有疯狂的配置,没有安装复杂的库,尽管它确实有一个库后备。只是工作。我在这里尝试了其他示例,但当有很多额外的设置要做或安装不起作用时,我感到很困惑!

            注意事项:

            sync-request 使用的示例在使用res.getBody() 时效果不佳,get body 所做的只是接受编码并转换响应数据。改用res.body.toString(encoding)

            【讨论】:

            • 我发现同步请求非常慢。我最终使用了另一个github.com/dhruvbird/http-sync,在我的情况下它快了 10 倍。
            • 我没有任何慢跑。这会产生一个子进程。您的系统使用多少 cpu,您使用的是什么版本的节点?我很想知道是否需要切换。
            • 我同意 Filip,这很慢。
            • 同样的事情我问过翻转但没有得到回应:你的系统使用了多少 cpu,你使用的是什么版本的节点?
            • 这会占用大量 CPU,不建议用于生产环境。
            【解决方案12】:

            Super Request

            这是另一个基于请求并使用承诺的同步模块。超级简单易用,适用于 mocha 测试。

            npm install super-request

            request("http://domain.com")
                .post("/login")
                .form({username: "username", password: "password"})
                .expect(200)
                .expect({loggedIn: true})
                .end() //this request is done 
                //now start a new one in the same session 
                .get("/some/protected/route")
                .expect(200, {hello: "world"})
                .end(function(err){
                    if(err){
                        throw err;
                    }
                });
            

            【讨论】:

              【解决方案13】:

              ...4 年后...

              这是框架Danf的原始解决方案(您不需要任何代码来处理这种事情,只需要一些配置):

              // config/common/config/sequences.js
              
              'use strict';
              
              module.exports = {
                  executeMySyncQueries: {
                      operations: [
                          {
                              order: 0,
                              service: 'danf:http.router',
                              method: 'follow',
                              arguments: [
                                  'www.example.com/api_1.php',
                                  'GET'
                              ],
                              scope: 'response1'
                          },
                          {
                              order: 1,
                              service: 'danf:http.router',
                              method: 'follow',
                              arguments: [
                                  'www.example.com/api_2.php',
                                  'GET'
                              ],
                              scope: 'response2'
                          },
                          {
                              order: 2,
                              service: 'danf:http.router',
                              method: 'follow',
                              arguments: [
                                  'www.example.com/api_3.php',
                                  'GET'
                              ],
                              scope: 'response3'
                          }
                      ]
                  }
              };
              

              对要并行执行的操作使用相同的order 值。

              如果你想更短,你可以使用收集过程:

              // config/common/config/sequences.js
              
              'use strict';
              
              module.exports = {
                  executeMySyncQueries: {
                      operations: [
                          {
                              service: 'danf:http.router',
                              method: 'follow',
                              // Process the operation on each item
                              // of the following collection.
                              collection: {
                                  // Define the input collection.
                                  input: [
                                      'www.example.com/api_1.php',
                                      'www.example.com/api_2.php',
                                      'www.example.com/api_3.php'
                                  ],
                                  // Define the async method used.
                                  // You can specify any collection method
                                  // of the async lib.
                                  // '--' is a shorcut for 'forEachOfSeries'
                                  // which is an execution in series.
                                  method: '--'
                              },
                              arguments: [
                                  // Resolve reference '@@.@@' in the context
                                  // of the input item.
                                  '@@.@@',
                                  'GET'
                              ],
                              // Set the responses in the property 'responses'
                              // of the stream.
                              scope: 'responses'
                          }
                      ]
                  }
              };
              

              查看框架的overview 了解更多信息。

              【讨论】:

                【解决方案14】:

                我来到这里是因为我需要对 http.request 进行速率限制(约 10k 聚合查询到弹性搜索以构建分析报告)。以下只是让我的机器窒息。

                for (item in set) {
                    http.request(... + item + ...);
                }
                

                我的 URL 非常简单,因此这可能不适用于原始问题,但我认为对于遇到与我类似的问题并想要一个简单的 JavaScript 无库解决方案的读者来说,它可能适用且值得在这里写。

                我的工作不依赖于订单,我第一个解决这个问题的方法是将它包装在一个 shell 脚本中来分块(因为我是 JavaScript 新手)。这很实用,但并不令人满意。我最终的 JavaScript 解决方案是执行以下操作:

                var stack=[];
                stack.push('BOTTOM');
                
                function get_top() {
                  var top = stack.pop();
                  if (top != 'BOTTOM')
                    collect(top);
                }
                
                function collect(item) {
                    http.request( ... + item + ...
                    result.on('end', function() {
                      ...
                      get_top();
                    });
                    );
                }
                
                for (item in set) {
                   stack.push(item);
                }
                
                get_top();
                

                看起来collectget_top之间是相互递归的。我不确定它是否有效,因为系统是异步的,并且函数 collect 以在 on.('end'. 时为事件隐藏的回调完成。 p>

                我认为它足以适用于原始问题。如果像我的场景一样,序列/集合是已知的,则可以一步将所有 URL/键推入堆栈。如果它们是随手计算的,则 on('end' 函数可以在 get_top() 之前将下一个 url 压入堆栈。如果有的话,结果会更少嵌套,并且在您调用的 API 发生变化时可能更容易重构。

                我意识到这实际上等同于上面@generalhenry 的简单递归版本(所以我赞成!)

                【讨论】:

                  【解决方案15】:

                  Raynos 已经很好地回答了这个问题。然而,自发布答案以来,序列库发生了变化。

                  要使序列正常工作,请点击此链接:https://github.com/FuturesJS/sequence/tree/9daf0000289954b85c0925119821752fbfb3521e

                  这就是在npm install sequence 之后如何让它工作:

                  var seq = require('sequence').Sequence;
                  var sequence = seq.create();
                  
                  seq.then(function call 1).then(function call 2);
                  

                  【讨论】:

                    【解决方案16】:

                    此代码可用于同步和顺序执行一组 Promise,之后您可以在 .then() 调用中执行最终代码。

                    const allTasks = [() => promise1, () => promise2, () => promise3];
                    
                    function executePromisesSync(tasks) {
                      return tasks.reduce((task, nextTask) => task.then(nextTask), Promise.resolve());
                    }
                    
                    executePromisesSync(allTasks).then(
                      result => console.log(result),
                      error => console.error(error)
                    );
                    

                    【讨论】:

                      【解决方案17】:

                      截至 2018 年,使用 ES6 模块和 Promises,我们可以编写这样的函数:

                      import { get } from 'http';
                      
                      export const fetch = (url) => new Promise((resolve, reject) => {
                        get(url, (res) => {
                          let data = '';
                          res.on('end', () => resolve(data));
                          res.on('data', (buf) => data += buf.toString());
                        })
                          .on('error', e => reject(e));
                      });
                      

                      然后在另一个模块中

                      let data;
                      data = await fetch('http://www.example.com/api_1.php');
                      // do something with data...
                      data = await fetch('http://www.example.com/api_2.php');
                      // do something with data
                      data = await fetch('http://www.example.com/api_3.php');
                      // do something with data
                      

                      代码需要在异步上下文中执行(使用async关键字)

                      【讨论】:

                      • 这个答案迫切需要更多的支持。我只需将返回值稍微调整为resolve([res, data]),它允许调用者使用res.statusCode 获取返回状态代码。
                      【解决方案18】:

                      我实际上得到了你(和我)想要的,没有使用 await、Promises 或任何(外部)库(我们自己的除外)的包含。

                      这是怎么做的:

                      我们将制作一个 C++ 模块与 node.js 一起使用,该 C++ 模块函数将发出 HTTP 请求并将数据作为字符串返回,您可以通过以下方式直接使用它:

                      var myData = newModule.get(url);
                      

                      您准备好开始了吗?

                      第 1 步: 在你电脑的其他地方新建一个文件夹,我们只是用这个文件夹来构建 module.node 文件(从 C++ 编译),你可以稍后移动它。

                      在新文件夹中(我将我的放在 mynewFolder/src 中以进行整理):

                      npm init
                      

                      然后

                      npm install node-gyp -g
                      

                      现在制作 2 个新文件: 1,调用something.cpp并将此代码放入其中(或根据需要进行修改):

                      #pragma comment(lib, "urlmon.lib")
                      #include <sstream>
                      #include <WTypes.h>  
                      #include <node.h>
                      #include <urlmon.h> 
                      #include <iostream>
                      using namespace std;
                      using namespace v8;
                      
                      Local<Value> S(const char* inp, Isolate* is) {
                          return String::NewFromUtf8(
                              is,
                              inp,
                              NewStringType::kNormal
                          ).ToLocalChecked();
                      }
                      
                      Local<Value> N(double inp, Isolate* is) {
                          return Number::New(
                              is,
                              inp
                          );
                      }
                      
                      const char* stdStr(Local<Value> str, Isolate* is) {
                          String::Utf8Value val(is, str);
                          return *val;
                      }
                      
                      double num(Local<Value> inp) {
                          return inp.As<Number>()->Value();
                      }
                      
                      Local<Value> str(Local<Value> inp) {
                          return inp.As<String>();
                      }
                      
                      Local<Value> get(const char* url, Isolate* is) {
                          IStream* stream;
                          HRESULT res = URLOpenBlockingStream(0, url, &stream, 0, 0);
                      
                          char buffer[100];
                          unsigned long bytesReadSoFar;
                          stringstream ss;
                          stream->Read(buffer, 100, &bytesReadSoFar);
                          while(bytesReadSoFar > 0U) {
                              ss.write(buffer, (long long) bytesReadSoFar);
                              stream->Read(buffer, 100, &bytesReadSoFar);
                          }
                          stream->Release();
                          const string tmp = ss.str();
                          const char* cstr = tmp.c_str();
                          return S(cstr, is);
                      }
                      
                      void Hello(const FunctionCallbackInfo<Value>& arguments) {
                          cout << "Yo there!!" << endl;
                      
                          Isolate* is = arguments.GetIsolate();
                          Local<Context> ctx = is->GetCurrentContext();
                      
                          const char* url = stdStr(arguments[0], is);
                          Local<Value> pg = get(url,is);
                      
                          Local<Object> obj = Object::New(is);
                          obj->Set(ctx,
                              S("result",is),
                              pg
                          );
                          arguments.GetReturnValue().Set(
                             obj
                          );
                      
                      }
                      
                      void Init(Local<Object> exports) {
                          NODE_SET_METHOD(exports, "get", Hello);
                      }
                      
                      NODE_MODULE(cobypp, Init);
                      

                      现在在同一个目录中创建一个名为something.gyp 的新文件,并将(类似的东西)放入其中:

                      {
                         "targets": [
                             {
                                 "target_name": "cobypp",
                                 "sources": [ "src/cobypp.cpp" ]
                             }
                         ]
                      }
                      

                      现在在 package.json 文件中,添加:"gypfile": true,

                      现在:在控制台中,node-gyp rebuild

                      如果它通过整个命令并在最后说“ok”并且没有错误,那么你(几乎)很好,如果没有,那么留下评论..

                      但如果它有效,则转到 build/Release/cobypp.node(或任何它为您调用的),将其复制到您的主 node.js 文件夹,然后在 node.js 中:

                      var myCPP = require("./cobypp")
                      var myData = myCPP.get("http://google.com").result;
                      console.log(myData);
                      
                      ..
                      
                      response.end(myData);//or whatever
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2018-08-24
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2020-11-11
                        相关资源
                        最近更新 更多