#node.js #synchronization
#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. я не думаю, что вы можете сделать что-то лучше, кроме очистки этого.
2. Почему они должны быть в порядке?
3. @Raynos Вам могут понадобиться некоторые данные из api_1, прежде чем вы узнаете, что отправлять в api_2
4. Стоит упомянуть, что Futures довольно устарел, рассмотрите возможность использования более новой библиотеки, такой как Bluebird или Q.
5. Заголовок и вопрос противоречат друг другу. В вашем вопросе вы описываете не синхронный запрос, а последовательность запросов, каждый из которых обычно выполняется асинхронно. Большая разница — синхронный вызов блокируется, а последовательность асинхронных действий не блокируется (блокирует пользовательский интерфейс, блокирует сервер от обработки других запросов). Ниже приведен ответ с упоминанием
sync-request
библиотеки, который является хорошим ответом на название этого вопроса, но не ответом на то, что подразумевает код вопроса. Приведенный ниже ответ об обещаниях является лучшим ответом на этот вопрос. Что вы имели в виду?
Ответ №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) { })
...
})
Комментарии:
1. Пожалуйста, попробуйте IcedCoffeScript, в котором предусмотрены функции ожидания и отсрочки для nodejs.
2. Является ли это неблокирующим? Я имею в виду, что это блокирует следующую функцию в строке, но это не будет блокировать выполнение других асинхронных функций, не так ли?
3. Да, отложенные методы являются неблокирующими / асинхронными.
4. API обещаний ES6 должен эффективно заменить это, даже по мнению автора «Фьючерсов».
5. Futures очень старый и устаревший. Вместо этого смотрите вопрос.
Ответ №2:
Мне также нравится решение 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
});
Комментарии:
1. var http = require(‘http’);
2. Хах. example.com на самом деле это домен, предназначенный для такого рода вещей. Вау.
3. Код async.series не работает, по крайней мере, начиная с async версии 0.2.10. series() принимает только до двух аргументов и выполняет элементы первого аргумента как функции, поэтому async выдает ошибку при попытке выполнить объекты как функции.
4. Вы можете сделать что-то похожее на то, что предназначено для этого кода, используя ForEachAsync ( github.com/FuturesJS/forEachAsync ).
5. Это делает именно то, что я хотел. Спасибо!
Ответ №3:
синхронизация-запрос
Безусловно, самый простой из найденных мной и используемых — это 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)
вместо этого.
Комментарии:
1. Я обнаружил, что sync-request выполняется очень медленно.. В итоге я использовал другой github.com/dhruvbird/http-sync в моем случае это в 10 раз быстрее.
2. у меня не было никаких медленных запусков для этого. Это порождает дочерний процесс. Сколько процессоров использует ваша система и какую версию узла вы используете? Я хотел бы знать, чтобы определить, нужно ли мне переключаться или нет.
3. Я согласен с Филиппом, это медленно.
4. То же самое, что я спрашивал у flip, но не получил ответа: сколько процессоров использует ваша система и какую версию node вы используете?
5. это требует значительного объема процессора, не рекомендованного для производственного использования.
Ответ №4:
Вы могли бы сделать это, используя мою общую библиотеку узлов:
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');
Комментарии:
1. черт, я проголосовал за, думая, что это сработает, но это не так : (
require(...).HttpClient is not a constructor
Ответ №5:
Я бы использовал рекурсивную функцию со списком 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
});
Комментарии:
1. Этот метод я использовал, поскольку у меня есть переменный список запросов для выполнения (600 элементов и постоянно растет). Тем не менее, существует проблема с вашим кодом: событие ‘data’ будет отправляться несколько раз за запрос, если выходные данные API больше размера блока. Вы хотите «буферизировать» данные следующим образом: var body = «; res.on(‘data’,функция (data){ body = data; }).on(‘end’, функция(){ обратный вызов (body); if (API.длина) callAPIs(хост, API);});
2. Обновлено. Я просто хотел показать, как проблему можно упростить / сделать более гибкой с помощью рекурсии. Лично я всегда использую модуль request для такого рода задач, поскольку он позволяет с легкостью пропускать множественные обратные вызовы.
3. @generalhenry, как бы я это сделал, если бы захотел использовать модуль запроса? Можете ли вы предложить фрагмент кода, который выполняет вышеуказанное с помощью запроса?
4. Я добавил версию запроса и версию запроса / асинхронности.
Ответ №6:
Начиная с 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
ключевого слова)
Комментарии:
1. Этот ответ срочно нуждается в большем количестве голосов. Я бы просто немного изменил возврат,
resolve([res, data])
что позволяет вызывающим пользователям получать код состояния возврата с помощьюres.statusCode
.
Ответ №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:
Кажется, решения этой проблемы бесконечны, вот еще одно 🙂
// do it once.
sync(fs, 'readFile')
// now use it anywhere in both sync or async ways.
var data = fs.readFile(__filename, 'utf8')
Комментарии:
1. Хотя библиотека, на которую вы ссылались, ДЕЙСТВИТЕЛЬНО предлагает решение проблемы операционной системы, в вашем примере fs.ReadFile всегда синхронизирован.
2. Нет, вы можете явно указать обратный вызов и использовать его как асинхронную версию, если хотите.
3. однако пример был для http-запросов, а не для обмена данными с файловой системой.
Ответ №9:
используйте последовательно.
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!
Ответ №10:
Использование библиотеки запросов может помочь свести к минимуму количество ошибок:
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)
}
)
Ответ №11:
Существует множество библиотек потока управления — мне нравится 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
})
Ответ №12:
Райнос хорошо ответил на этот вопрос. Тем не менее, с момента публикации ответа в библиотеке последовательностей произошли изменения.
Чтобы заставить последовательность работать, перейдите по этой ссылке: 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);
Ответ №13:
Вот моя версия @andy-shin последовательно с аргументами в массиве вместо индекса:
function run(funcs, args) {
var i = 0;
var recursive = function() {
funcs[i](function() {
i ;
if (i < funcs.length)
recursive();
}, args[i]);
};
recursive();
}
Ответ №14:
…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'
}
]
}
};
Взгляните на обзор фреймворка для получения дополнительной информации.
Ответ №15:
Я попал сюда, потому что мне нужно было ограничить скорость http.request (~ 10 тыс. запросов на агрегацию в elastic search для построения аналитического отчета). Следующее просто заглушило мою машину.
for (item in set) {
http.request(... item ...);
}
Мои URL-адреса очень просты, поэтому это может быть неприменимо к исходному вопросу, но я думаю, что это потенциально применимо и стоит написать здесь для читателей, которые сталкиваются здесь с проблемами, похожими на мои, и которым нужно тривиальное решение на JavaScript без библиотеки.
Моя работа не зависела от порядка, и мой первый подход к ее выполнению заключался в том, чтобы обернуть это в сценарий оболочки, чтобы разбить его на части (потому что я новичок в 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. Я не уверен, что это действует, потому что система асинхронна, и функция collect завершается обратным вызовом, сохраненным для события в on.(‘end’.
Я думаю, что это достаточно общее, чтобы применить к исходному вопросу. Если, как в моем сценарии, последовательность / набор известны, все URL-адреса / ключи могут быть помещены в стек за один шаг. Если они вычисляются по ходу выполнения, функция on(‘end’ может отправить следующий URL-адрес в стек непосредственно перед get_top(). Во всяком случае, результат имеет меньшую вложенность, и его может быть проще реорганизовать при изменении вызываемого вами API.
Я понимаю, что это эффективно эквивалентно простой рекурсивной версии @generalhenry выше (поэтому я поддержал это!)
Ответ №16:
Супер запрос
Это еще один синхронный модуль, который основан на запросе и использует обещания. Супер простой в использовании, хорошо работает с тестами 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;
}
});
Ответ №17:
Этот код можно использовать для синхронного и последовательного выполнения массива обещаний, после чего вы можете выполнить свой окончательный код в .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)
);
Ответ №18:
На самом деле я получил именно то, что вы (и я) хотели, без использования await, Promises или включений какой-либо (внешней) библиотеки (кроме нашей собственной).
Вот как это сделать:
Мы собираемся создать модуль C для работы с node.js , и эта функция модуля C выполнит HTTP-запрос и вернет данные в виде строки, и вы можете использовать это напрямую, выполнив:
var myData = newModule.get(url);
ВЫ ГОТОВЫ приступить к работе?
Шаг 1: создайте новую папку где-нибудь еще на вашем компьютере, мы используем эту папку только для сборки модуля.файл узла (скомпилирован с 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, amp;stream, 0, 0);
char buffer[100];
unsigned long bytesReadSoFar;
stringstream ss;
stream->Read(buffer, 100, amp;bytesReadSoFar);
while(bytesReadSoFar > 0U) {
ss.write(buffer, (long long) bytesReadSoFar);
stream->Read(buffer, 100, amp;bytesReadSoFar);
}
stream->Release();
const string tmp = ss.str();
const char* cstr = tmp.c_str();
return S(cstr, is);
}
void Hello(const FunctionCallbackInfo<Value>amp; 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