Синхронный запрос в Node.js
Если мне нужно вызвать 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) {
});
});
}
});
});
}
});
});
}
Ответы
Ответ 1
Использование отложенных слов, таких как 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) { })
...
})
Ответ 2
Мне также нравится решение 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
});
Ответ 3
Вы можете сделать это, используя мою общую 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');
Ответ 4
К сожалению, самый простой из них я нашел и использовал sync-request и поддерживает как node, так и браузер!
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()
, все, что получает тело, принимает кодировку и конвертировать данные ответа. Просто сделайте res.body.toString(encoding)
.
Ответ 5
Я бы использовал рекурсивную функцию со списком 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 );
edit: request/async version
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
});
Ответ 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
Ответ 7
Другая возможность - настроить обратный вызов, который отслеживает завершенные задачи:
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();
}
Затем просто присвойте идентификатор каждому из них, и вы можете настроить свои требования для выполнения задач перед закрытием соединения.
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 не обеспечивает самые основные синхронные вызовы. Но я понимаю, что приманка для асинхронности.
Ответ 8
используйте последовательно.
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!
Ответ 9
Использование библиотеки 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)
}
)
Ответ 10
Есть много библиотек потока управления - мне нравится conseq (... потому что я его написал). Кроме того, on('data')
может срабатывать несколько раз, поэтому используйте библиотеку обложек REST, например restler.
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
})
Ответ 11
На это хорошо ответил Рейнос. Тем не менее в библиотеке последовательностей произошли изменения, так как ответ был опубликован.
Чтобы получить последовательность работы, перейдите по этой ссылке: 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);
Ответ 12
Здесь моя версия @andy-shin последовательно с аргументами в массиве вместо индекса:
function run(funcs, args) {
var i = 0;
var recursive = function() {
funcs[i](function() {
i++;
if (i < funcs.length)
recursive();
}, args[i]);
};
recursive();
}
Ответ 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 '@@[email protected]@' in the context
// of the input item.
'@@[email protected]@',
'GET'
],
// Set the responses in the property 'responses'
// of the stream.
scope: 'responses'
}
]
}
};
Взгляните на overview рамки для получения дополнительной информации.
Ответ 14
Я приземлился здесь, потому что мне нужно было ограничить скорость http.request(~ 10 тыс. запросов агрегации к эластичному поиску для создания аналитического отчета). Следующие просто задушили мою машину.
for (item in set) {
http.request(... + item + ...);
}
Мои URL-адреса очень просты, поэтому это не может быть тривиально применимо к исходному вопросу, но я думаю, что он потенциально применим и стоит писать здесь для читателей, которые приземляются здесь с проблемами, подобными моим, и которым требуется тривиальное решение для библиотеки без библиотеки.
Моя работа не зависела от заказа, и мой первый подход к обучению заключался в том, чтобы обернуть ее в оболочку script, чтобы ее обрезать (потому что я новичок в 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();
Похоже на взаимную рекурсию между collect и get_top. Я не уверен, что это действует, потому что система асинхронна, и сбор функции завершается с обратным вызовом, спрятанным для события at on. ('End'.
Я думаю, что достаточно общего, чтобы обратиться к первому вопросу. Если, как и мой сценарий, последовательность/набор известна, все URL-адреса/клавиши могут быть перенесены в стек за один шаг. Если они вычисляются по ходу, функция on ('end' может вызывать следующий url в стеке непосредственно перед get_top(). Во всяком случае, результат имеет меньше вложенности и может быть проще рефакторировать, когда API, который вы вызываете изменения.
Я понимаю, что это фактически эквивалентно простой рекурсивной версии @generalhenry (так что я сохранил это!)
Ответ 15
Это еще один синхронный модуль, основанный на запросе и использующий promises. Супер прост в использовании, хорошо работает с мокко-тестами.
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;
}
});