Requête synchrone dans Node.js

Si je dois appeler l’API 3 http dans un ordre séquentiel, quelle serait la 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) { }); }); } }); }); } }); }); } 

En utilisant des différés comme les Futures à 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 à travers la scope alors faites simplement quelque chose comme ça

  .then(function(next, d) { http.get({}, function(res) { next(res, d); }); }) .then(function(next, res, d) { }) ... }) 

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

https://github.com/caolan/async

Selon que vous avez besoin des résultats dans chaque fonction suivante, j’utiliserais soit la série, le parallèle ou la cascade.

Série 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 ultérieur.

Parallèlement, s’ils peuvent être exécutés en parallèle, vous n’avez pas besoin des résultats de chaque fonction parallèle et vous avez besoin d’un rappel lorsque tout est terminé.

Cascade si vous voulez transformer les résultats dans 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 }); 

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

demande de synchronisation

De loin, le plus simple que j’ai trouvé et utilisé est la demande de synchronisation et prend en charge à la fois le nœud et le navigateur!

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

Ca y est, pas de configuration folle, pas de lib complexes à installer, bien qu’il y ait un repli de lib. Ça marche J’ai essayé d’autres exemples ici et j’étais déconcerté quand il y avait beaucoup de configuration supplémentaire à faire ou que les installations ne fonctionnaient pas!

Remarques:

L’exemple que sync-request utilise ne joue pas bien lorsque vous utilisez res.getBody() , tout ce que fait get body accepte un encodage et convertit les données de réponse. res.body.toSsortingng(encoding) simplement res.body.toSsortingng(encoding) place.

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

edit: demande 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 ); 

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

Il semble que les solutions à ce problème soient sans fin, en voici une autre 🙂

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

Une autre possibilité est de configurer un rappel qui effectue le suivi des 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, atsortingbuez simplement un ID à chacun et vous pouvez définir vos besoins pour les tâches à effectuer 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, c’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é.

utiliser le séquençage.

sudo npm install 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 aussi 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! 

L’utilisation de la bibliothèque de requêtes peut aider à minimiser les pertes:

 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 un maximum de précision, vous devriez essayer une bibliothèque de stream de contrôle comme Step – elle vous permettra également de paralléliser les requêtes, en supposant que cela soit 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) } ) 

Il y a beaucoup de bibliothèques de stream de contrôle – j’aime conseq (… parce que je l’ai écrit.) En outre, on('data') peut déclencher plusieurs fois, donc utilisez une bibliothèque d’encapsulation REST 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 }) 

Cela a été bien répondu par Raynos. Pourtant, la bibliothèque de séquences a été modifiée depuis que la réponse a été publiée.

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

Voici comment vous pouvez le faire fonctionner après la npm install sequence :

 var seq = require('sequence').Sequence; var sequence = seq.create(); seq.then(function call 1).then(function call 2); 

Voici ma version de @ andy-shin séquentiellement avec des arguments dans un tableau au lieu d’un index:

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

…4 ans plus tard…

Voici une solution originale avec le framework Danf (vous n’avez pas besoin de code pour ce genre de choses, seulement de la configuration):

 // config/common/config/sequences.js 'use ssortingct'; 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 d’ order pour les opérations que vous souhaitez exécuter en parallèle.

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

 // config/common/config/sequences.js 'use ssortingct'; 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' } ] } }; 

Jetez un coup d’oeil à la vue d’ ensemble du framework pour plus d’informations.

J’ai atterri ici parce que je devais évaluer-limit http.request (~ 10k requêtes d’agrégation à la recherche élastique 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 ne s’applique pas forcément à la question originale, mais je pense que cela est potentiellement applicable et mérite d’être écrit ici pour les lecteurs qui rencontrent des problèmes similaires aux miens et qui veulent une solution JavaScript sans bibliothèque.

Mon travail ne dépendait pas de l’ordre et ma première approche pour éviter cela consistait à l’envelopper dans un script shell pour le fragmenter (parce que je ne connaissais pas JavaScript). C’était fonctionnel mais pas satisfaisant. Ma résolution JavaScript à la fin consistait à effectuer les opérations suivantes:

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

Cela ressemble à une récursivité mutuelle entre collect et get_top . Je ne suis pas sûr que cela soit effectif car le système est asynchrone et que la fonction de collecte se termine avec un rappel caché pour l’événement à la fin .

Je pense que c’est assez général pour s’appliquer à la question originale. Si, comme mon scénario, la séquence / ensemble est connue, toutes les URL / clés peuvent être poussées sur la stack en une seule étape. S’ils sont calculés au fur et à mesure, la fonction on (‘end’ peut pousser l’URL suivante sur la stack juste avant get_top () . Si le résultat est moins nested, il peut être plus facile de le refactoriser lorsque l’API que vous appelez changements.

Je me rends compte que cela équivaut effectivement à la version simple récursive de @ generalhenry ci-dessus (donc j’ai voté cela!)

Super demande

Ceci est un autre module synchrone basé sur la demande et utilisant des promesses. Super simple à utiliser, fonctionne bien avec les 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; } });