Node.js'de eşzamanlı istek


99

Sıralı sırayla 3 http API çağırmam gerekirse, aşağıdaki koda göre daha iyi bir alternatif ne olabilir:

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


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

Bunu temizlemek dışında, bundan daha iyisini yapabileceğini sanmıyorum.
hvgotcodes

2
Neden sıralı olmaları gerekiyor?
Raynos

11
@Raynos api_2'ye ne göndereceğinizi bilmeden önce api_1'den bazı verilere ihtiyacınız olabilir
andyortlieb

9
Futures'ın oldukça kullanımdan kaldırıldığını belirtmekte fayda var, Bluebird veya Q gibi daha yeni bir kitaplık kullanmayı düşünün.
Benjamin Gruenbaum

1
Başlık ve soru birbiriyle çelişiyor. Sorunuzda eşzamanlı bir istek değil, normalde her biri eşzamansız olarak gerçekleşen bir dizi istek açıklıyorsunuz. Büyük fark - eşzamanlı çağrı blokları ve bir dizi eşzamansız eylem engellemez (kullanıcı arayüzünü engelleyin, sunucunun diğer istekleri işlemesini engelleyin). Aşağıda sync-requestkütüphaneden bahseden bir cevap var, bu sorunun başlığına iyi bir cevap, ancak soru kodunun ne anlama geldiğine bir cevap değil. Promises ile ilgili aşağıdaki cevap bunun için daha iyi bir cevaptır. Hangisini kastettin?
Jake

Yanıtlar:


69

Gibi ertelenenleri kullanma 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) {
    ...
  })

Dürbünü geçmeniz gerekiyorsa, sadece böyle bir şey yapın

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

Lütfen düğümler için bekleme ve erteleme sağlayan IcedCoffeScript'i deneyin.
Thanigainathan

Bu engellemez mi? Demek istediğim, sıradaki bir sonraki işlev için engelliyor ama bu diğer eşzamansız işlevlerin yürütülmesini engellemeyecek, değil mi?
Oktav

1
Evet, ertelenen yöntemler engellemez / eşzamansızdır.
dvlsg

4
"Futures" kitabının yazarına göre bile ES6 Promise API etkin bir şekilde bunun yerini almalı
Alexander Mills

Vadeli işlemler çok eski ve kullanımdan kaldırıldı. Bunun yerine q bakın.
Jim Aho

53

Raynos'un çözümünü de seviyorum ama farklı bir akış kontrol kitaplığı tercih ediyorum.

https://github.com/caolan/async

Sonraki her işlevde sonuçlara ihtiyacınız olup olmadığına bağlı olarak, seri, paralel veya şelale kullanırdım.

Seri olarak çalıştırılmaları gerektiğinde seriler , ancak sonraki her işlev çağrısında sonuçlara mutlaka ihtiyacınız yoktur.

Paralel olarak paralel olarak yürütülebilirlerse, her paralel işlev sırasında her birinin sonuçlarına ihtiyacınız yoktur ve tümü tamamlandığında bir geri aramaya ihtiyacınız vardır.

Her işlevde sonuçları değiştirmek ve bir sonrakine geçmek istiyorsanız şelale

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 = gerekli ('http');
Elle Mundy

7
Hah. example.com aslında bu tür şeyler için tasarlanmış bir alan adıdır. Vay.
meawoppl

Async.series kodu, en azından async v0.2.10'dan itibaren çalışmıyor. series () yalnızca en fazla iki argüman alır ve ilk argümanın öğelerini işlev olarak çalıştırır, bu nedenle async, nesneleri işlev olarak çalıştırmaya çalışırken bir hata atar.
kapak

1
ForEachAsync ( github.com/FuturesJS/forEachAsync ) kullanarak bu kodla amaçlanana benzer bir şey yapabilirsiniz .
kapak

Bu tam olarak istediğimi yapıyor. Teşekkür ederim!
aProperFox

33

Bunu Ortak Düğüm kitaplığımı kullanarak yapabilirsiniz :

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
bok, işe yarayacağını düşünerek oy verdim ve işe yaramıyor :(require(...).HttpClient is not a constructor
moeiscool

30

senkronizasyon isteği

Şimdiye kadar bulduğum ve kullandığım en kolay olanı senkronizasyon isteği ve hem düğümü hem de tarayıcıyı destekliyor!

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

İşte bu, çılgın konfigürasyon yok, karmaşık kitaplık yüklemeleri yok, ancak bir lib geri dönüşü var. Sadece çalışıyor. Burada diğer örnekleri denedim ve yapılacak çok fazla kurulum olduğunda veya kurulumlar işe yaramadığında şaşkına döndüm!

Notlar:

Eşitleme isteğinin kullandığı örnek, kullandığınızda iyi oynamıyor res.getBody(), tüm get body'nin yaptığı bir kodlamayı kabul etmek ve yanıt verilerini dönüştürmektir. Onun res.body.toString(encoding)yerine yap.


Eşitleme isteğinin çok yavaş olduğunu buldum .. Başka bir github.com/dhruvbird/http-sync kullandım ki bu benim durumumda 10 kat daha hızlı.
Filip Spiridonov

onun için hiç yavaş koşmadım. Bu bir çocuk süreci doğurur. Sisteminiz kaç cpus kullanıyor ve hangi düğüm sürümünü kullanıyorsunuz? Geçiş yapmam gerekip gerekmediğini belirlemek isterim.
jemiloii

Filip'e katılıyorum, bu yavaş.
Rambo7

Flip'e sorduğum ancak yanıt alamadığım şey aynı: Sisteminiz kaç cpus kullanıyor ve hangi düğüm sürümünü kullanıyorsunuz?
jemiloii

bu, üretimde kullanım için tavsiye edilmeyen ciddi miktarda CPU kullanır.
moeiscool

20

Bir apis listesiyle özyinelemeli bir işlev kullanırdım

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

düzenleme: sürüm isteği

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

düzenleme: istek / eşzamansız sürüm

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

Bu, yapmam gereken değişken bir istek listesine sahip olduğum için kullandığım yöntemdir (600 öğe ve büyüyen). Bununla birlikte, kodunuzla ilgili bir sorun var: API çıktısı yığın boyutundan büyükse, 'veri' olayı istek başına birden çok kez yayınlanır. Verileri şu şekilde "arabelleğe almak" istiyorsunuz: var body = ''; res.on ('data', function (data) {body + = data;}). on ('end', function () {callback (body); if (APIs.length) callAPIs (host, API'ler);} );
Ankit Aggarwal

Güncellenmiş. Sorunun özyineleme yoluyla nasıl daha basit / daha esnek hale getirilebileceğini göstermek istiyordum. Kişisel olarak, çoklu geri aramayı kolaylıkla atlamanıza izin verdiği için bu tür şeyler için her zaman istek modülünü kullanıyorum.
generalhenry

@generalhenry, istek modülünü kullanmak istersem bunu nasıl yaparım? İsteği kullanarak yukarıdakilere ulaşan bir kod parçacığı sunabilir misiniz?
Scotty

Bir istek sürümü ve bir istek / eşzamansız sürüm ekledim.
generalhenry

5

Görünüşe göre bu sorunun çözümü hiç bitmiyor, işte bir tane daha :)

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


DOES bağladığınız kitaplık, OP'nin sorununa bir çözüm sunsa da, sizin örneğinizde fs.readFile her zaman eşitlenir.
Eric

1
Hayır, geri aramayı açıkça sağlayabilir ve isterseniz eşzamansız sürüm olarak kullanabilirsiniz.
Alex Craft

1
örnek, dosya sistemi iletişimi değil, http istekleri içindi.
Seth

5

Diğer bir olasılık, tamamlanan görevleri izleyen bir geri arama ayarlamaktır:

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

Ardından her birine bir kimlik atayın ve bağlantıyı kapatmadan önce tamamlanması gereken görevler için gereksinimlerinizi ayarlayabilirsiniz.

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

Tamam, hiç hoş değil. Sıralı aramalar yapmanın başka bir yoludur. NodeJS'nin en temel senkronize çağrıları sağlamaması talihsiz bir durumdur. Ama eşzamansızlığın cazibesinin ne olduğunu anlıyorum.


4

sıralı kullanın.

sudo npm yükleme sırası

veya

https://github.com/AndyShin/sequenty

Çok basit.

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

ayrıca şöyle bir döngü kullanabilirsiniz:

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

İstek kitaplığını kullanmak, kazaları en aza indirmeye yardımcı olabilir:

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

Ancak maksimum mükemmellik için Step gibi bir kontrol akışı kitaplığını denemelisiniz - bu aynı zamanda kabul edilebilir olduğunu varsayarak istekleri paralelleştirmenize de izin verecektir:

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 itibariyle ES6 modüllerini ve Promises'i kullanarak şöyle bir fonksiyon yazabiliriz:

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

ve sonra başka bir modülde

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

Kodun eşzamansız bir bağlamda yürütülmesi gerekir ( asyncanahtar sözcük kullanılarak )


2

Çok sayıda kontrol akışı kitaplığı var - conseq'i seviyorum (... çünkü yazdım.) Ayrıca, on('data')birkaç kez ateşleyebilir, bu nedenle restler gibi bir REST sarıcı kitaplığı kullanın .

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


1

İşte benim @ andy-shin sürümüm, dizin yerine dizideki argümanlarla sırayla:

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

1

...4 yıl sonra...

İşte Danf çerçevesi ile orijinal bir çözüm (bu tür şeyler için herhangi bir koda ihtiyacınız yoktur, sadece bazı yapılandırmalara ihtiyacınız vardır):

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

orderParalel olarak yürütülmesini istediğiniz işlemler için aynı değeri kullanın .

Daha da kısaltmak istiyorsanız, bir toplama işlemi kullanabilirsiniz:

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

Daha fazla bilgi için çerçeveye genel bakışa bir göz atın .


1

Buraya geldim çünkü http.request'i (analitik bir rapor oluşturmak için elastik aramaya ~ 10.000 toplama sorgusunu) oran-sınırlamam gerekiyordu. Aşağıdakiler makinemi boğdu.

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

URL'lerim çok basittir, bu nedenle bu, asıl soru için önemsiz bir şekilde geçerli olmayabilir, ancak bence benimkine benzer sorunlarla buraya gelen ve önemsiz bir JavaScript kitaplıksız çözüm isteyen okuyucular için hem potansiyel olarak uygulanabilir hem de burada yazmaya değer.

İşim siparişe bağlı değildi ve bunu yapmak için ilk yaklaşımım onu ​​bir kabuk betiğine sararak parçalamaktı (çünkü JavaScript'te yeniyim). Bu işlevseldi ama tatmin edici değildi. Sonunda benim JavaScript çözümüm aşağıdakileri yapmaktı:

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 ve get_top arasında karşılıklı bir özyineleme gibi görünüyor . Etkili olduğundan emin değilim çünkü sistem eşzamansızdır ve toplama işlevi on olay için saklanan bir geri arama ile tamamlanır . ('End' .

Orijinal soruya başvurmanın yeterince genel olduğunu düşünüyorum. Senaryom gibi, sıra / küme biliniyorsa, tüm URL'ler / anahtarlar tek adımda yığın üzerinde itilebilir. Siz ilerledikçe hesaplanırlarsa, on ('end' işlevi yığındaki sonraki url'yi get_top () 'dan hemen önce itebilir. . Herhangi bir şey varsa, sonuç daha az iç içe geçer ve aradığınız API'yi yeniden düzenlemek daha kolay olabilir değişiklikler.

Bunun, @ generalhenry'nin yukarıdaki basit özyinelemeli sürümüne etkili bir şekilde eşdeğer olduğunu fark ettim (bu yüzden buna olumlu oy verdim!)


0

Süper İstek

Bu, isteğe bağlı olan ve vaatleri kullanan başka bir senkronize modüldür. Kullanımı süper basit, mocha testleri ile iyi çalışıyor.

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

Bu kod, bir dizi vaatleri eşzamanlı ve sıralı olarak yürütmek için kullanılabilir ve ardından çağrıdaki son kodunuzu çalıştırabilirsiniz .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

Beklenti, Sözler veya herhangi bir (harici) kitaplık (bizimki hariç) kullanmadan tam olarak senin (ve benim) istediğini aldım.

İşte bunu nasıl yapacağınız:

Node.js ile birlikte kullanmak için bir C ++ modülü yapacağız ve bu C ++ modül işlevi HTTP isteğini yapacak ve verileri bir dizge olarak döndürecek ve bunu doğrudan aşağıdakileri yaparak kullanabilirsiniz:

var myData = newModule.get(url);

Başlamak için HAZIR MISINIZ ?

Adım 1: Bilgisayarınızda başka bir yerde yeni bir klasör oluşturun, bu klasörü yalnızca module.node dosyasını (C ++ 'dan derlenmiş) oluşturmak için kullanıyoruz, daha sonra taşıyabilirsiniz.

Yeni klasörde (benimkini organize etmek için mynewFolder / src içine koydum):

npm init

sonra

npm install node-gyp -g

şimdi 2 yeni dosya yapın: 1, bir şey.cpp olarak adlandırılır ve bu kodu içine koymak için (veya isterseniz değiştirin):

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

Şimdi aynı dizinde yeni bir dosya oluşturun something.gypve içine şunu koyun (buna benzer bir şey):

{
   "targets": [
       {
           "target_name": "cobypp",
           "sources": [ "src/cobypp.cpp" ]
       }
   ]
}

Şimdi package.json dosyasında şunu ekleyin: "gypfile": true,

Şimdi: konsolda, node-gyp rebuild

Tüm komuttan geçiyorsa ve sonunda hata olmadan "tamam" diyorsa, (neredeyse) gitmekte fayda var, yoksa bir yorum bırakın ..

Ancak çalışırsa, build / Release / cobypp.node'a gidin (veya sizin için ne gerekiyorsa), onu ana node.js klasörünüze, ardından node.js'ye kopyalayın:

var myCPP = require("./cobypp")
var myData = myCPP.get("http://google.com").result;
console.log(myData);

..

response.end(myData);//or whatever
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.