2017-12-26 13 views
0

私はJavascriptで始まりましたが、forループをループしているときにこのコードを同期させるにはどうすればいいですか? 基本的には、ループ内で複数のPOSTリクエストを作成してから、ライブラリX-Rayを使用してデータをスクラップし、結果をMongoデータベースに保存しています。 出力はOKですが、順不同で突然ハングアップするので、Ctrl + Cを押して強制的に閉じる必要があります。これは私の関数である:ノードjsとfoorループでの同期複数要求

function getdata() { 
    const startYear = 1996; 
    const currentYear = 1998; // new Date().getFullYear() 

for (let i = startYear; i <= currentYear; i++) { 
for (let j = 1; j <= 12; j++) { 
    if (i === startYear) { 
    j = 12; 
    } 

    // Form to be sent 
    const form = { 
    year: `${i}`, 
    month: `${j}`, 
    day: '01', 
    }; 

    const formData = querystring.stringify(form); 
    const contentLength = formData.length; 

    // Make HTTP Request 
    request({ 
    headers: { 
     'Content-Length': contentLength, 
     'Content-Type': 'application/x-www-form-urlencoded', 
    }, 
    uri: 'https://www.ipma.pt/pt/geofisica/sismologia/', 
    body: formData, 
    method: 'POST', 
    }, (err, res, html) => { 

    if (!err && res.statusCode === 200) { 

     // Scrapping data with X-Ray 
     x(html, '#divID0 > table > tr', { 
     date: '.block90w', 
     lat: 'td:nth-child(2)', 
     lon: 'td:nth-child(3)', 
     prof: 'td:nth-child(4)', 
     mag: 'td:nth-child(5)', 
     local: 'td:nth-child(6)', 
     degree: 'td:nth-child(7)', 
     })((error, obj) => { 

     const result = { 
      date: obj.date, 
      lat: obj.lat.replace(',', '.'), 
      lon: obj.lon.replace(',', '.'), 
      prof: obj.prof == '-' ? null : obj.prof.replace(',', '.'), 
      mag: obj.mag.replace(',', '.'), 
      local: obj.local, 
      degree: obj.degree, 
     }; 

     // console.log(result); 

     upsertEarthquake(result); // save to DB 

     }); 

    } 


    }); 

    } 
    } 
    } 

私は約束やコールバックを使用する必要がありますが、私はこれを行う方法を理解できないと思いますし、私はすでに待っていますが、成功しません非同期を使用してみました。追加情報を提供する必要がある場合は、感謝してください。

答えて

-1

ループ内でリクエストを呼び出しています。

非同期関数は、メインスレッドのロジックが終了した後に結果(A.K.A.、コールバック関数で応答を受け取る)を取得する関数です。

この方法で、我々は持っている場合は、この:

for (var i = 0; i < 12; i++) { 
    request({ 
     data: i 
    }, function(error, data) { 
     // This is the request result, inside a callback function 
    }); 
} 

ロジックがコールバックを呼び出す前に、12のrequestの上で実行するようになりますので、コールバックは、すべてのメインループが実行された後に積層して呼び出されます。

すべてのES6ジェネレータに入ることなく(少し複雑になり、低レベルで何が起こっているのかがあなたにとってより良いと思うので)、requestに電話するか、彼のコールバック関数が呼び出されるのを待って、次にrequestを呼び出します。どうやってするか?たくさんの方法がありますが、通常は次のようになります。

var i= 0; 
function callNext() { 
    if (i>= 12) { 
     requestEnded(); 
    } else { 
     request({ 
      data: i++ // Increment the counter as we are not inside a for loop that increments it 
     }, function(error, data) { 
      // Do something with the data, and also check if an error was received and act accordingly, which is very much possible when talking about internet requests 
      console.log(error, data); 
      // Call the next request inside the callback, so we are sure that the next request is ran just after this request has ended 
      callNext(); 
     }) 
    } 
} 
callNext(); 

requestEnded() { 
    console.log("Yay"); 
} 

ここではロジックが表示されます。次のコールを行うcallNextという名前の関数があります。コールがもう必要ない場合はrequestEndedにコールします。 requestcallNext内で呼び出されると

、それはコールバックが受けられるようにするために、(いつか将来的には、非同期的に起こるであろう)お待ちしております、受信したデータを処理し、その後、コールバックの内側にもう一度callNextを呼び出すために彼に言います。

+0

を使用している場合、私はそれを実装しようとしたが、それはもうハングしないにも関わらず、出力はまだ、順不同ています。あなたは私が何をしたかここで見ることができますhttps://pastebin.com/J0PdG9rv –

+0

@MiguelFerreiraあなたはそれを持っています。この情報を使って、非同期コールバックを連結する方法を作成しました。それはクールですが、まだ問題があります。別の非同期要求内に非同期要求があります。 'request'コールバックの中では' x'を呼び出します。これには非同期コールバックもあります。すべての非同期コールバックが完了したら、次のリクエストを実行します。私はあなたのペーストビンをちょっと微調整しました:https://pastebin.com/keshVjXxあなたが望む結果を得た直後に、私が 'getdata'をどこで呼び出すかチェックしてください。この時点で完了し、次のリクエストを続行できます。 –

+0

'x'関数を呼び出さないようなエラーや何かが得られた場合、次のリクエストを続行しないことに注意してください。これがあなたが実際に望むものなのかどうか考えてみてください。おそらく、このような状況が発生した場合は、要求を繰り返すか、最初からやり直すことができます。それはあなた次第です。ちょうど 'else'を追加して、"要求チェーン "が壊れた場合に実行したいコードを追加してください。 –

-1

ループの代わりに、開始年と終了年を使用して配列を作成してから、リクエストのconfigにマップし、その結果をX線が返す場所にマップすることができます(x-rayはpromise likeを返します。 )。次に、約束を返す関数を使用して、モンゴブにスクレープの結果を入れます。

何かが拒否された場合は、Failタイプのオブジェクトを作成し、そのオブジェクトで解決します。

すべてのリクエスト(x-rayとmongoをPromise.allを使用して並行して開始しますが、throttleを使用してアクティブなリクエストの量を制限します)。ここで

はそれがコード内でどのように見えるかです:

こすると、多くが標的部位は、あなたがAY期間のためのxの要求以上のものを作り、あなたのIPをブラックリストを開始することはできませんということです何が起こる
//you can get library containing throttle here: 
// https://github.com/amsterdamharu/lib/blob/master/src/index.js 
const lib = require('lib'); 
const Fail = function(details){this.details=details;}; 
const isFail = o=>(o&&o.constructor)===Fail; 
const max10 = lib.throttle(10); 
const range = lib.range; 
const createYearMonth = (startYear,endYear)=> 
    range(startYear,endYear) 
    .reduce(
    (acc,year)=> 
     acc.concat(
     range(1,12).map(month=>({year,month})) 
    ) 
    ,[] 
); 
const toRequestConfigs = yearMonths => 
    yearMonths.map(
    yearMonth=>{ 
     const formData = querystring.stringify(yearMonth); 
     return { 
     headers: { 
      'Content-Length': formData.length, 
      'Content-Type': 'application/x-www-form-urlencoded', 
     }, 
     uri: 'https://www.ipma.pt/pt/geofisica/sismologia/', 
     body: formData, 
     method: 'POST', 
     }; 
    } 
); 
const scrape = html => 
    x(
    html, 
    '#divID0 > table > tr', 
    { 
     date: '.block90w', 
     lat: 'td:nth-child(2)', 
     lon: 'td:nth-child(3)', 
     prof: 'td:nth-child(4)', 
     mag: 'td:nth-child(5)', 
     local: 'td:nth-child(6)', 
     degree: 'td:nth-child(7)' 
    } 
); 
const requestAsPromise = config => 
    new Promise(
    (resolve,reject)=> 
     request(
     config, 
     (err,res,html)=> 
      (!err && res.statusCode === 200) 
      //x-ray returns a promise: 
      // https://github.com/matthewmueller/x-ray#xraythencb 
      ? resolve(html) 
      : reject(err) 
    ) 
); 
const someMongoStuff = scrapeResult => 
    //do mongo stuff and return promise 
    scrapeResult; 
const getData = (startYear,endYear) => 
    Promise.all(
    toRequestConfigs(
     createYearMonth(startYear,endYear) 
    ) 
    .map(
     config=> 
     //maximum 10 active requests 
     max10(requestAsPromise)(config) 
     .then(scrape) 
     .then(someMongoStuff) 
     .catch(//if something goes wrong create a Fail type object 
      err => new Fail([err,config.body]) 
     ) 
    ) 
) 
//how to use: 
getData(1980,1982) 
.then(//will always resolve unless toRequestConfigs or createYearMonth throws 
    result=>{ 
    //items that were successfull 
    const successes = result.filter(item=>!isFail(item)); 
    //items that failed 
    const failed = result.filter(isFail); 
    } 
) 

あなたがそれを越えるとサービスを拒否します。

そして、あなたが上記のコード変更できるのは、あなたが5秒あたり10のリクエストに制限したいとしましょう:あなたは内部async methodssync for...loopを持っているコードの残りの部分は同じ

+1

時間と労力に感謝しますが、ジョージは答えが早かったし、解決策も簡単でした。 –

+0

@MiguelFerreira問題解決に役立つ必要がある場合は、この解決策の実装に役立つ必要があるかどうかを教えてください。これは、抑制並列がどのようにスクレイピングに関連するか、または正常に処理されたアイテムがすべて失われたかまたは命令型プログラミングと宣言型プログラミングの違いを理解しているためです。 – HMR

-1

ある

const max10 = lib.throttlePeriod(10,5000); 

それは問題です。この問題を解決するために

きれいな方法は、あなたがこのような何かのためにあなたのコードを変更する必要がありupsertEarthquake(result)後、あなたはすべての反復を停止すると仮定すると、

ES2017 async/await構文

です。

function async getdata() { 
    const startYear = 1996; 
    const currentYear = 1998; // new Date().getFullYear() 

    for (let i = startYear; i <= currentYear; i++) { 
     for (let j = 1; j <= 12; j++) { 
      if (i === startYear) 
       j = 12; 

      // Form to be sent 
      const form = { 
       year: `${i}`, 
       month: `${j}`, 
       day: '01', 
      }; 

      const formData = querystring.stringify(form); 
      const contentLength = formData.length; 
      //Make HTTP Request 
      await new Promise((next, reject)=> { 
       request({ 
        headers: { 
         'Content-Length': contentLength, 
         'Content-Type': 'application/x-www-form-urlencoded', 
        }, 
        uri: 'https://www.ipma.pt/pt/geofisica/sismologia/', 
        body: formData, 
        method: 'POST', 
       }, (err, res, html) => { 
        if (err || res.statusCode !== 200) 
         return next() //If there is an error jump to the next 

        //Scrapping data with X-Ray 
        x(html, '#divID0 > table > tr', { 
         date: '.block90w', 
         lat: 'td:nth-child(2)', 
         lon: 'td:nth-child(3)', 
         prof: 'td:nth-child(4)', 
         mag: 'td:nth-child(5)', 
         local: 'td:nth-child(6)', 
         degree: 'td:nth-child(7)', 
        })((error, obj) => { 
         const result = { 
          date: obj.date, 
          lat: obj.lat.replace(',', '.'), 
          lon: obj.lon.replace(',', '.'), 
          prof: obj.prof == '-' ? null : obj.prof.replace(',', '.'), 
          mag: obj.mag.replace(',', '.'), 
          local: obj.local, 
          degree: obj.degree, 
         } 
         //console.log(result); 
         upsertEarthquake(result); // save to DB 
         next() //This makes jump to the next for... iteration 
        }) 

       }) 
      } 
     } 
    } 
} 

私はupsertEarthquakeは非同期関数であるかのタイプの火災で、忘れていることを前提としています。

そこにあなたがnext()を使用することができ、エラーがあるが、あなたはループを切断したい場合は、reject()

if (err || res.statusCode !== 200) 
    return reject(err) 
関連する問題