Node.jsの同期リクエスト


99

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


          });
        });
        }
      });
    });
    }
  });
});
}

それを片付ける以外に、あなたはそれ以上のことができるとは思いません。
hvgotcodes、2011年

2
なぜ彼らは秩序がある必要があるのですか?
Raynos、

11
@Raynos api_2に何を送信するかを知る前に、api_1からいくつかのデータが必要になる場合があります
andyortlieb

9
それの価値は先物がかなり廃止されて、ブルーバードまたはQ.などの新しいライブラリ使用することを検討していることに言及
ベンジャミンGruenbaum

1
タイトルと質問は互いに矛盾しています。質問では同期リクエストについて説明していませんが、通常はそれぞれ非同期で発生する一連のリクエストです。大きな違い-同期呼び出しはブロックし、非同期アクションのシーケンスはブロックしません(UIをブロックし、サーバーが他の要求を処理するのをブロックします)。sync-requestライブラリについて言及している以下の回答があります。これは、この質問のタイトルに対する良い回答ですが、質問のコードが意味するものに対する回答ではありません。以下のPromiseに関する答えは、そのためのより良い答えです。どういう意味?
ジェイク

回答:


69

のような遅延オブジェクトを使用する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) { })
    ...
  })

nodejsの待機と延期を提供するIcedCoffeScriptを試してください。
Thanigainathan 2014

これは非ブロッキングですか?インラインで次の関数をブロックしていますが、これは他の非同期関数の実行をブロックしませんか?
Oktav 2014年

1
はい、遅延メソッドは非ブロッキング/非同期です。
dvlsg 2014年

4
ES6 Promise APIは、「Futures」の著者によると、これを効果的に置き換える必要があります
Alexander Mills

Futuresは非常に古く、非推奨です。代わりにqを参照してください。
ジム・アホ

53

私はレイノスのソリューションも好きですが、別のフロー制御ライブラリを好みます。

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

9
var http = require( 'http');
Elle Mundy 2013年

7
ああ。example.comは、実際にはこの種のもののために設計されたドメインです。ワオ。
meawoppl 2014年

async.seriesコードは、少なくともasync v0.2.10以降では機能しません。series()は最大2つの引数のみを取り、最初の引数の要素を関数として実行するため、asyncはオブジェクトを関数として実行しようとするとエラーをスローします。

1
forEachAsync(github.com/FuturesJS/forEachAsync)を使用して、このコードで意図されているものと同様のことができます。
ふた

これはまさに私が欲しかったことです。ありがとうございました!
aProperFox 2015

33

あなたは私のCommon 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');

3
がらくた、私はそれがうまくいくと思って賛成しましたが、うまくいきません:(require(...).HttpClient is not a constructor
moeiscool

30

同期リクエスト

私が見つけて使用した中で最も簡単なのは同期リクエストで、ノードとブラウザの両方をサポートしています!

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()。すべてのget bodyが行うのは、エンコーディングを受け入れて応答データを変換することだけです。ただ、やるres.body.toString(encoding)代わりに。


sync-requestが非常に遅いことがわかりました。私の場合、10 倍速い別のgithub.com/dhruvbird/http-syncを使用してしまいました。
Filip Spiridonov、2015年

私はそのための遅い実行がありませんでした。これにより、子プロセスが生成されます。システムはいくつのCPUを使用しており、どのバージョンのノードを使用していますか?切り替える必要があるかどうかを判断したいのですが。
jemiloii

私はフィリップに同意します、これは遅いです。
Rambo7

私がフリップに尋ねたのと同じことですが、応答がありませんでした。システムが使用しているCPUの数と使用しているノードのバージョンは何ですか。
jemiloii 2016年

これはかなりの量のCPUを使用するため、実稼働での使用は推奨されません。
moeiscool 2017

20

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

これは、リクエストの変数リスト(600アイテム以上)があるため、私が採用した方法です。つまり、コードに問題があります。API出力がチャンクサイズより大きい場合、リクエストごとに 'data'イベントが複数回発行されます。次のようにデータを「バッファリング」したいとします。var body = ''; res.on( 'data'、function(data){body + = data;})。on( 'end'、function(){callback(body); if(APIs.length)callAPIs(host、APIs);} );
Ankit Aggarwal

更新しました。再帰によって問題がどのように単純化/柔軟化されるかを示したかっただけです。個人的には、複数のコールバックを簡単にスキップできるため、常にこの種の要求モジュールを使用しています。
Generalhenry

@generalhenry、リクエストモジュールを使用したい場合はどうすればいいですか?リクエストを使用して上記を実現するコードスニペットを提供できますか?
スコッティ

リクエストバージョンとリクエスト/非同期バージョンを追加しました。
Generalhenry 2013

5

この問題の解決策は永遠に続くようです、もう1つあります:)

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


リンクしたライブラリはOPの問題の解決策を提供しますが、例では、fs.readFileは常に同期されます。
エリック

1
いいえ、必要に応じてコールバックを明示的に指定し、非同期バージョンとして使用できます。
Alex Craft

1
この例は、HTTPリクエストの場合であり、ファイルシステム通信ではありません。
Seth

5

もう1つの可能性は、完了したタスクを追跡するコールバックを設定することです。

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

次に、それぞれにIDを割り当てるだけで、接続を閉じる前に完了する必要のあるタスクの要件を設定できます。

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

さて、それはきれいではありません。これは、順次呼び出しを行うもう1つの方法です。NodeJSが最も基本的な同期呼び出しを提供しないのは残念です。しかし、私は非同期性に対する魅力が何であるかを理解しています。


4

シーケンシーを使用します。

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!

3

リクエストライブラリを使用すると、問題を最小限に抑えることができます。

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

3

2018年以降、ES6モジュールとPromiseを使用して、次のような関数を記述できます。

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キーワードを使用)


2

制御フローライブラリはたくさんあります-私はconseqが好きです(...書いたからです)。また、on('data')何度も起動する可能性があるため、restlerのようなRESTラッパーライブラリを使用してください

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

これはレイノスによってよく答えられました。しかし、回答が投稿されて以来、シーケンスライブラリに変更が加えられています。

シーケンスを機能させるには、次のリンクに従ってください: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);

1

これが、@ andy-shinの私のバージョンで、引数の代わりに配列の引数を使用しています。

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

1

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

詳細については、フレームワークの概要をご覧ください。


1

http.requestをレート制限する必要があったため、ここに着陸しました(分析レポートを作成するために、弾性検索への約10kの集約クエリ)。以下はちょうど私のマシンを詰まらせた。

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

collectget_top間の相互再帰のように見えます。システムが非同期であり、関数collectがオンのイベント用に隠されたコールバックで完了するため、それが有効であるかどうかはわかりません。( 'end'

元の質問に適用するのに十分一般的だと思います。私のシナリオのように、シーケンス/セットがわかっている場合、すべてのURL /キーを1つのステップでスタックにプッシュできます。それらがあなたが行くように計算される場合、on( 'end'関数はget_top()の直前にスタックの次のURLをプッシュできます。どちらかと言えば、結果はネストが少なくなり、呼び出しているAPIのリファクタリングがより簡単になるかもしれません変更。

これは、@ generalhenryの上記の単純な再帰バージョンと実質的に同等であることを理解しています(そのため、私はそれを支持しました!)


0

スーパーリクエスト

これは、リクエストに基づいており、promiseを使用する別の同期モジュールです。使い方は非常に簡単で、モカテストでうまく動作します。

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

このコードを使用して、Promiseの配列を同期的かつ順次に実行した後、.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

私は実際には、あなた(そして私)が望んでいたものを正確に手に入れました。

方法は次のとおりです。

node.jsに対応するC ++モジュールを作成します。そのC ++モジュール関数はHTTPリクエストを作成し、データを文字列として返します。次のようにして直接使用できます。

var myData = newModule.get(url);

開始する準備はできていますか?

ステップ1:コンピューターの別の場所に新しいフォルダーを作成します。このフォルダーは、(C ++からコンパイルされた)module.nodeファイルの作成にのみ使用し、後で移動できます。

新しいフォルダ(整理のために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, &stream, 0, 0);

    char buffer[100];
    unsigned long bytesReadSoFar;
    stringstream ss;
    stream->Read(buffer, 100, &bytesReadSoFar);
    while(bytesReadSoFar > 0U) {
        ss.write(buffer, (long long) bytesReadSoFar);
        stream->Read(buffer, 100, &bytesReadSoFar);
    }
    stream->Release();
    const string tmp = ss.str();
    const char* cstr = tmp.c_str();
    return S(cstr, is);
}

void Hello(const FunctionCallbackInfo<Value>& 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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.