Node.js中的同步请求


99

如果我需要按顺序调用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) { 


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

除了清理之外,我认为您不能做得更好。
hvgotcodes 2011年

2
为什么它们需要井井有条?
雷诺斯

11
@Raynos在知道要发送到api_2的内容之前,您可能需要api_1的一些数据
andyortlieb 2012年

9
值得一提的是,期货是相当不赞成,请考虑使用像蓝鸟或Q.更新库
本杰明Gruenbaum

1
标题和问题相互矛盾。您不是在问题中描述同步请求,而是描述一系列请求,通常每个请求都是异步发生的。最大的不同是-同步调用会阻塞,而一系列异步操作不会阻塞(阻塞UI,阻塞服务器处理其他请求)。下面有一个提到sync-request库的答案,这是对这个问题的标题的一个很好的答案,而不是对该问题代码含义的答案。下面关于承诺的答案是一个更好的答案。你是什​​么意思
杰克

Answers:


69

使用像这样的延期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提供等待和延迟。
Thanigainathan 2014年

这是非阻塞的吗?我的意思是它正在阻止下一个函数,但这不会阻止其他异步函数的执行,是吗?
奥克塔夫2014年

1
是的,延迟方法是非阻塞/异步的。
dvlsg 2014年

4
ES6 Promise API应该有效地取代了它,即使根据《未来》的作者
Alexander Mills

期货已经过时且过时了。请参阅q。
Jim Aho

53

我也喜欢Raynos的解决方案,但我更喜欢另一个流量控制库。

https://github.com/caolan/async

根据您是否需要每个后续函数的结果,我将使用序列,并行或瀑布式。

系列时,他们已被串行执行,但你并不一定需要在以后的每个函数调用的结果。

如果它们可以并行执行,则是并行的,您不需要在每个并行函数期间获得每个函数的结果,并且在所有函数完成时都需要回调。

如果要使每个函数的结果变形并传递给下一个函数,则为瀑布式

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
});

9
var http = require('http');
Elle Mundy 2013年

7
哈哈 example.com实际上是为此类事物设计的域。哇。
meawoppl 2014年

async.series代码不起作用,至少从async v0.2.10开始。series()仅占用两个参数,并将第一个参数的元素作为函数执行,因此async尝试将对象作为函数执行时会引发错误。

1
您可以使用forEachAsync(github.com/FuturesJS/forEachAsync)执行类似于此代码预期的操作

这正是我想要的。谢谢!
aProperFox

33

您可以使用我的Common Node库执行此操作:

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');

3
废话,我赞成认为它会工作,但没有:(require(...).HttpClient is not a constructor
moeiscool

30

同步请求

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

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

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

笔记:

当您使用时,sync-request使用的示例效果不好res.getBody(),所有get body所做的就是接受编码并转换响应数据。做吧res.body.toString(encoding)


我发现,同步请求很慢..我已经结束了使用另一个github.com/dhruvbird/http-sync这是在我的情况快10倍。
菲利普·斯皮里多诺夫

我没有任何慢跑。这产生一个子进程。您的系统使用了多少cpus,并且您使用的是哪个版本的节点?我很想知道确定我是否需要切换。
jemiloii 2015年

我同意Filip,这很慢。
Rambo7 '16

我问过翻页机,但没有任何反应:您的系统使用了多少个CPU,您使用的是哪个版本的节点?
jemiloii '16

这会占用大量CPU,不建议用于生产环境。
moeiscool

20

我将使用带有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);} );
Ankit Aggarwal,2012年

更新。我只是想展示如何通过递归使问题变得更简单/更灵活。就我个人而言,我总是将请求模块用于此类操作,因为它使您可以轻松地跳过多个回调。
generalhenry 2012年

@generalhenry,如果我想使用请求模块,我该怎么办?您能否提供可以满足上述使用要求的代码段?
Scotty

我添加了一个请求版本和一个请求/异步版本。
generalhenry 2013年

5

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

// 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


尽管您链接的DOES库提供了解决OP问题的解决方案,但在您的示例中,fs.readFile始终是同步的。
埃里克

1
不,您可以显式提供回调,并根据需要将其用作异步版本。
Alex Craft

1
该示例虽然适用于http请求,但不适用于文件系统通信。
赛斯

5

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

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分配一个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没有提供最基本的同步调用。但是我知道异步的诱因是什么。


4

顺序使用。

sudo npm安装顺序

要么

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!

3

使用请求库可以最大程度地减少麻烦:

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)
    }
)

3

从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关键字)


2

有很多控制流库-我喜欢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
  })


1

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

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

1

... 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'
            }
        ]
    }
};

查看框架概述以获取更多信息。


1

我登陆这里是因为我需要对http.request进行速率限制(约1万个聚合查询以进行弹性搜索以构建分析报告)。以下只是扼杀了我的机器。

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之间是相互递归的。我不确定它是否有效,因为系统是异步的,并且功能收集完成,并且在事件发生时隐藏了回调。('end'

我认为这足以适用于原始问题。如果像我的情况一样,序列/集合是已知的,则所有URL /关键字都可以一步被压入堆栈。如果按需计算,则on('end'函数可以在get_top()之前将堆栈中的下一个URL推送。如果有的话,结果嵌套较少,并且在调用API时可能更易于重构变化。

我意识到这实际上等效于上述@generalhenry的简单递归版本(因此我赞成!)


0

超级请求

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

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;
        }
    });

0

此代码可用于同步和顺序执行一个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)
);

0

实际上,我确实得到了您(和我)想要的东西,而没有使用wait,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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.