質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

Q&A

解決済

1回答

1959閲覧

node.jsでのループの同期処理について

tokoroten_12

総合スコア24

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

0グッド

0クリップ

投稿2020/08/17 04:55

編集2020/08/17 05:51

nodejsのrequestを使用したHTTPリクエストをループ分を使っての同期処理をしたいと考えています。

現在HTTPリクエストを以下のように行っています。

const request = require('request') const base_url = 'https://hogehoge/' function main(){ request(base_url , (e, response, body) => { if (e) { console.error(e) } // 取得したHTTPの処理 http_get(body) } } main()

このソースにfor文によるループを追加し「hogehoge/」「hogeoge/1」「hogehoge/2」…と順番にアクセスするための処理を追加しましたが意図した通りの順番にアクセスする動作を行ってくれません。
requestの処理が終わる前にforのループが抜けてしまうのか「hogehoge/4」ばかりにアクセスするなどの現象が起きています。

const request = require('request') const list_url= 'https://hogehoge/' function main(){ // for文の追加 for(let page_num = 1; page_num < 5; page_num++){ request(list_url, (e, response, body) => { if (e) { console.error(e) } // 取得したHTTPの処理 http_get(body) // URLに変数を加えて次のページへ list_url = list_url + String(page_num) } } } main()

そこで質問なのですがfor文の中の処理が全て終わったのを確認してから再度ループを行うような、ループの同期処理を行う方法はありますでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

結論から言えば
Promiseasync / await構文を勉強してください。

現在使っているrequestモジュールはPromise非対応です

仮にrequest-promise-nativeにしようと決定したとして、こんな感じになります。

bash

1# request-promise-nativeをインストール 2$ npm install request-promise-native

js

1const request = require('request-promise-native') 2const list_url= 'https://hogehoge/' 3 4// async関数を定義することで内部でawaitを使用可能となる 5async function main(){ 6 for(let page_num = 1; page_num < 5; page_num++){ 7 try { 8 body = await request(list_url) 9 http_get(body) 10 // list_urlはconst定義されてるから動かないんじゃね? 11 list_url = list_url + String(page_num) 12 } catch (e) { 13 console.error(e) 14 } 15 } 16} 17main()

なんでこうなるのかを順を追って解説します。

まずJavaScriptはシングルスレッドなので待つという事はしません。
sleep関数はありませんし、HTTPリクエストを送信したら帰って来るまで待ったりしません。
待ってるとブラウザが固まってフリーズしますから。
Node.jsもシングルスレッドなので同様、フリーズするので待てない。

いやいや、JSってボタンがクリックされたらHTML書き換える言語やないか、待ってるだろ!
待つためには仕組みが必要です。


「待つ」を実現するために、JSにはイベントという仕組みが存在します。
JSにはイベント置き場があり、達成条件と関数(達成後実行して欲しい処理)をセットで登録出来るようになっています。

質問文のrequest(list_url, fn)がそうですね。
これはイベント登録申請が受理されただけで、その行の動作自体は一瞬で完了します。
もしfor文で10件回すと、瞬時に10件のイベント登録申請が完了されてしまいます。

JSはすべての行の実行を行い動作を終了させると、
イベントループという待機状態に入ります。

イベントループでは達成条件を満たしたか否かを巡回して、
達成したイベントが見つかった場合、対になっている関数を一つ取り出して実行します。
そしてまた巡回に戻る。


なので質問文を愚直にやるならばfor文は使えません。

関数の中に関数を入れて、その中にまた関数を入れるという入れ子構造を作っておき、
それをrequestのコールバック関数として指定する形になります。

js

1request(list_url, (e, response, body) => { 2 http_get(body) 3 page_num = 0 4 list_url = list_url + String(page_num) 5 request(list_url, (e, response, body) => { 6 http_get(body) 7 page_num = 1 8 list_url = list_url + String(page_num) 9 request(list_url, (e, response, body) => { 10 http_get(body) 11 page_num = 2 12 list_url = list_url + String(page_num) 13 request(list_url, (e, response, body) => { 14 http_get(body) 15 }) 16 }) 17 }) 18})

4回リクエストを飛ばす仕組みはこんな感じ
これをコールバック地獄と呼びます。

そこで、JSの新しい仕様のES2016でPromiseが導入されました。
使い方に関しては勉強してほしいのですが、
それによりコードが多少簡素になりました。

js

1// Promise対応モジュール 2const request = require('request-promise-native') 3 4request(list_url) 5 .then(body => { 6 http_get(body) 7 page_num = 0 8 list_url = list_url + String(page_num) 9 return request(list_url) 10 }) 11 .then(body => { 12 http_get(body) 13 page_num = 1 14 list_url = list_url + String(page_num) 15 return request(list_url) 16 }) 17 .then(body => { 18 http_get(body) 19 page_num = 2 20 list_url = list_url + String(page_num) 21 return request(list_url) 22 }) 23 .then(body => { 24 http_get(body) 25 })

さっきよりはまだ……うん、って感じにはなりましたね。
この.thenがイベント登録なのでPromiseインスタンスに対して
forで.thenの登録を一気にやれば実現の可能性も見えてきます。

ですが、所詮イベント登録なので直感的にfor文を使う事は出来ません。
そこでようやくasync/await構文です。

awaitはPromiseインスタンスの実行完了を待ち、
自動的に.thenを叩いて中の値を抽出してくれる糖衣構文です。
回答冒頭のような事が可能となり、直感的なコードを書けるようになります。

投稿2020/08/18 03:35

miyabi-sun

総合スコア21203

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tokoroten_12

2020/08/18 23:43

回答ありがとうございます。 requestをPromiseでラップ?してasync、awaitを使う事でfor文でのループ処理が出来るようになりました。 function samplerequest(num){ return new Promise(resolve => { request(list_url, (e, response, body) => { http_get(body) resolve() }) }) } async function main(i){ for(let i=0; i<5; i++){ await samplerequest(i) list_url = base_url + String(i) } }
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問