Requête synchrone dans le nœud.js

Si j'ai besoin d'appeler 3 API http dans un ordre séquentiel, quelle serait une meilleure alternative au code suivant:

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


          });
        });
        }
      });
    });
    }
  });
});
}
88
demandé sur Lesleyvdp 2011-05-18 21:17:24

16 réponses

À l'Aide de deferreds comme 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) {
    ...
  })

Si vous avez besoin de passer la portée, faites quelque chose comme ceci

  .then(function(next, d) {
    http.get({}, function(res) {
      next(res, d);
    });
  })
  .then(function(next, res, d) { })
    ...
  })
64
répondu Raynos 2011-05-18 17:29:40

J'aime aussi la solution de Raynos, mais je préfère une bibliothèque de contrôle de flux différente.

Https://github.com/caolan/async

Selon que vous avez besoin des résultats dans chaque fonction suivante, j'utiliserais series, parallel ou waterfall.

Series quand ils doivent être exécutés en série, mais vous n'avez pas nécessairement besoin des résultats dans chaque appel de fonction suivant.

Parallel s'ils peuvent être exécutés en parallèle, vous n'avez pas besoin les résultats de chacun au cours de chaque fonction parallèle, et vous avez besoin d'un rappel quand tous ont terminé.

Cascade si vous voulez pour transformer les résultats de chaque fonction et passer à la suivante

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
});
51
répondu Josh 2014-05-17 09:40:56

Vous pouvez le faire en utilisant mA bibliothèque de nœuds communs :

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');
30
répondu Oleg 2012-04-07 20:11:16

Sync-demande de

De loin le plus facile que j'ai trouvé et utilisé est sync-request et il prend en charge à la fois node et le navigateur!

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

C'est tout, pas de configuration folle, pas d'installations lib complexes, bien qu'il ait un repli lib. Fonctionne, tout simplement. J'ai essayé d'autres exemples ici et j'ai été perplexe quand il y avait beaucoup de configuration supplémentaire à faire ou que les installations ne fonctionnaient pas!

Notes:

L'exemple que sync-demande de utilise ne joue pas agréable quand vous utilisez res.getBody(), Tout ce que get body fait est d'accepter un encodage et de convertir les données de réponse. Faites simplement res.body.toString(encoding) à la place.

26
répondu jemiloii 2015-03-19 17:57:38

J'utiliserais une fonction récursive avec une liste d'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 );

Modifier: demander la version

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

Modifier: demande / version asynchrone

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
});
20
répondu generalhenry 2013-04-25 15:12:36

Il semble que les solutions à ce problème soient sans fin, en voici une de plus:)

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

5
répondu Alexey Petrushin 2012-04-19 15:21:11

Une autre possibilité est de configurer un rappel qui suit les tâches terminées:

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

Ensuite, il suffit d'attribuer un ID à chacun et vous pouvez définir vos besoins pour lesquels les tâches doivent être terminées avant de fermer la connexion.

const var REQUEST_API1 = 0x01;
const var REQUEST_API2 = 0x02;
const var REQUEST_API3 = 0x03;
const var requestsNeeded = REQUEST_API1 | REQUEST_API2 | REQUEST_API3;

OK, ce n'est pas joli. C'est juste une autre façon de faire des appels séquentiels. Il est regrettable que NodeJS ne fournisse pas les appels synchrones les plus élémentaires. Mais je comprends ce que l'attrait est à l'asynchronicité.

5
répondu Nate 2012-09-11 14:13:01

Utilisez sequenty.

Sudo npm installer sequenty

Ou

Https://github.com/AndyShin/sequenty

Très simple.

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

Vous pouvez également utiliser une boucle comme ceci:

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!
4
répondu Andy Shin 2013-11-25 18:55:36

L'utilisation de la bibliothèque request peut aider à minimiser le cruft:

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

Mais pour une awesomeness maximale, vous devriez essayer une bibliothèque de flux de contrôle comme Step-elle vous permettra également de paralléliser les requêtes, en supposant que c'est acceptable:

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
répondu Ricardo Tomasi 2011-05-19 00:47:09

Il y a beaucoup de flux de contrôle bibliothèques -- j'aime conseq (... parce que je l'ai écrit.) En outre, on('data') peut se déclencher plusieurs fois, utilisez donc une bibliothèque REST wrapper comme 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
  })
2
répondu nornagon 2011-05-21 00:08:29

Cela a été bien répondu par Raynos. Pourtant, il y a eu des changements dans la bibliothèque de séquences depuis que la réponse a été publiée.

Pour que la séquence fonctionne, suivez ce lien: https://github.com/FuturesJS/sequence/tree/9daf0000289954b85c0925119821752fbfb3521e .

C'est comment vous pouvez le faire fonctionner après npm install sequence:

var seq = require('sequence').Sequence;
var sequence = seq.create();

seq.then(function call 1).then(function call 2);
2
répondu adityah 2017-04-12 17:01:55

Voici ma version de @andy-shin successivement avec des arguments dans array au lieu de index:

function run(funcs, args) {
    var i = 0;
    var recursive = function() {
        funcs[i](function() {
            i++;
            if (i < funcs.length)
                recursive();
        }, args[i]);
    };
    recursive();
}
1
répondu wieczorek1990 2014-11-03 11:19:47

...4 ans plus tard...

Voici une solution originale avec le cadre Danf (vous n'avez pas besoin de code pour ce genre de choses, seulement certaines config):

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

Utilisez la même valeur order pour les opérations que vous souhaitez exécuter en parallèle.

Si vous voulez être encore plus courte, vous pouvez utiliser un processus de collecte:

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

Prendre un coup d'oeil à la vue d'ensemble du cadre pour plus d'informations.

1
répondu Gnucki 2015-12-17 14:42:15

J'ai atterri ici parce que j'avais besoin de limiter la vitesse http.requête (~10k requêtes d'agrégation à Elastic search pour construire un rapport analytique). Ce qui suit vient d'étouffer ma machine.

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

Mes URL sont très simples, donc cela peut ne pas s'appliquer trivialement à la question originale, mais je pense que c'est à la fois potentiellement applicable et intéressant d'écrire ici pour les lecteurs qui atterrissent ici avec des problèmes similaires au mien et qui veulent une solution sans bibliothèque JavaScript triviale.

Mon travail n'était pas dépendant de l'ordre et ma première approche pour cela était de l'envelopper dans un script shell pour le découper (parce que je suis nouveau en JavaScript). C'était fonctionnel mais pas satisfaisant. Ma résolution JavaScript à la fin était de faire ce qui suit:

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

, Il ressemble à la récursivité mutuelle entre les recueillir et get_top. Je ne suis pas sûr que ce soit en effet parce que le système est asynchrone et que la fonction collect se termine par un rappel caché pour l'événement à sur.('fin'.

Je pense que c'est assez général pour s'appliquer à la question initiale. Si, comme mon scénario, la séquence/l'ensemble est connu, toutes les URL/clés peuvent être poussées sur la pile en une seule étape. Si elles sont calculées au fur et à mesure, la fonction on('end' peut pousser l'url suivante sur la pile juste avant get_top(). Si quoi que ce soit, le résultat a moins d'imbrication et peut être plus facile à refactoriser lorsque l'API que vous appelez change.

Je me rends compte que c'est effectivement équivalent à la version récursive simple de @ generalhenry ci-dessus (donc j'ai upvoted cela!)

1
répondu irwinj 2016-01-28 00:26:42

Super Demande De

C'est un autre module synchrone qui est basé sur request et utilise des promesses. Super simple à utiliser, fonctionne bien avec des tests de moka.

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
répondu jemiloii 2015-08-04 15:42:04

Ce code peut être utilisé pour exécuter un tableau de promesses de manière synchrone et séquentielle après quoi vous pouvez exécuter votre code final dans l'appel .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
répondu galatians 2018-10-01 18:12:54