Синхронный запрос в 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

К сожалению, самый простой из них я нашел и использовал 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;
        }
    });