Node.js中的同步请求

如果我需要按顺序调用3个http API,那么下面的代码是更好的select:

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

使用像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) { }) ... }) 

我也喜欢Raynos的解决scheme,但我更喜欢不同的stream量控制库。

https://github.com/caolan/async

根据你是否需要每个后续function的结果,我要么使用系列,平行或瀑布。

系列,当它们必须被连续执行时,但是你不一定需要每个后续函数调用的结果。

如果可以并行执行并行,则在每个并行函数期间不需要每个并行函数的结果,并且在完成所有并行函数时需要callback。

瀑布,如果你想在每个function变形的结果,并传递到下一个

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

你可以使用我的公共节点库来做到这一点:

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

同步请求

到目前为止,我find和使用的最容易的是同步请求 ,它支持节点和浏览器!

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

就是这样,没有疯狂的configuration,没有复杂的lib安装,虽然它有一个lib的后备。 只是工作。 我在这里尝试了其他的例子,当有很多额外的设置或安装没有工作的时候,它被难倒了!

笔记:

sync-request使用的示例在使用res.getBody()res.getBody() ,所有get body都接受编码并转换响应数据。 只需做res.body.toString(encoding)

我会使用recursion函数与apis列表

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

编辑:请求/asynchronous版本

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

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

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

另一种可能性是build立一个跟踪已完成的任务的callback:

 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,您可以在closures连接之前设置完成哪些任务的需求。

 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没有提供最基本的同步调用。 但我明白诱惑是不同步的。

使用顺序。

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! 

使用请求库可以帮助减less垃圾:

 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这样的控制stream库 – 它也允许你并行化请求,假设它是可以接受的:

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

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

雷诺斯已经很好地回答了这个问题。 答案已经发布,但是序列库已经发生了变化。

要获得序列的工作,请点击此链接: 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); 

这里是@ andy-shin的版本,而且数组中的参数是索引而不是索引:

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

… 4年后…

这是一个使用框架Danf的原始解决scheme(你不需要任何代码,只需要一些configuration):

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

看看框架的概述了解更多信息。

我在这里登陆,因为我需要限制http.request(〜10k聚合查询到弹性search来构build分析报告)。 以下只是呛住了我的机器。

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

我的url非常简单,所以这可能并不适用于原来的问题,但我认为这是可能适用的,值得写在这里,为读者提供类似于我的问题,并希望一个简单的JavaScript无库解决scheme。

我的工作并不依赖于顺序,我的第一个方法就是把它封装在一个shell脚本中来组装它(因为我是JavaScript新手)。 这是function,但不令人满意。 我的JavaScriptparsing到底是为了做到以下几点:

 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之间的相互recursion。 我不确定它是否有效,因为系统是asynchronous的,并且函数collect完成,并且callback函数保存在on('end')中

我认为这是足够普遍适用于原来的问题。 如果像我的场景一样,序列/集合是已知的,则所有的URL /密钥都可以一步推入堆栈。 如果按照计算方式计算, on('end')函数可以在get_top()之前将堆栈中的下一个url推送出去,如果有的话,结果会减less嵌套,并且在调用API时可能更容易重构变化。

我意识到这实际上相当于@ generalhenry的简单recursion版本(所以我upvoted!)

超级请求

这是另一个基于请求和使用承诺的同步模块。 超级简单的使用,与摩卡testing效果很好。

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