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

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

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

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

Q&A

解決済

2回答

927閲覧

ファイルを順番にアップロードしたい

SystemAjisai

総合スコア171

Node.js

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

0グッド

0クリップ

投稿2019/05/22 07:19

前提・実現したいこと

サーバーでファイルを受付けてファイル内のデータを計算・DBに登録するプログラムが既に動いています。
それに対して端末内のファイルを送信するバッチを作りたいです。
送信対象は2000以上あり、ファイルがある端末は14台あります。
CSVは1日1ファイルあり、ファイル名が日付になっています。
1台の端末内のファイルは日付が古い順にアップロードする必要があります。
古い順にアップロードする理由は、サーバー側の計算処理に前日のデータが必要なためです。
この処理は経常的に動かすものではなく、過去データを収集するのが目的です。

処理は
0. その端末内の送信対象ファイルのパスリストを作る(日付順)
0. リストをループさせながら、送信⇒レスポンスが返ってくるまで待つ
0. 次のファイルを送信する

という順で書いて、レスポンスを待つ部分はasyncawaitを使いました。
試しに5ファイルを対象にサーバー側のCPUを監視しながら実行してみたのですが、開始・終了のログが5ファイル分一瞬で出力され、とてもレスポンスを待っているように見えません。
(サーバー側の処理は1ファイル辺り5秒ぐらいかかります)

それでも日付順に処理されるならいいかなと思ったのですが、サーバー側でもログを出してみると、バッチ側では日付順に送っているのに、その順番で処理されていませんでした。

どうやったら前のリクエストのレスポンスが返ってきてから次のリクエストを送信できるでしょうか?

該当のソースコード

サーバー側(抜粋)

入力チェックや計算処理など関係なさそうなところは省略しています。

javascript

1router.post('/upload', upload.single('file'), async (req, res)=>{ 2 const { file } = req; 3 let client; 4 try{ 5 client = await MongoClient.connect(DB_URL, {useNewUrlParser: true}); 6 const db = client.db("DB名"); 7 8 // ★デバッグ用のログ 9 console.log("処理開始:"+ file.originalname); 10 11 // DB登録処理(省略) 12 // 計算処理(省略) 13 // 計算結果の登録処理(省略) 14 15 } catch (err) { 16 console.log(err); 17 } finally { 18 if (client) client.close(); 19 } 20 res.status(200).send("OK"); 21})

バッチ側

javascriptですが、.batファイルに書いて実行してます。

javascript

1//&cls&node %0 %1&pause > nul&exit 2 3const fs = require('fs') 4const path = require('path'); 5const axios = require('axios'); 6const FormData = require('form-data'); 7 8const URL = "アップロード先"; 9const FOLDER_PATH = "過去データのファイルパス"; 10 11const uploadfnc = async(path) =>{ 12 let ary = path.split("/"); 13 ary = ary.slice(-4); 14 let form = new FormData(); 15 form.append('key1', ary[0]); 16 form.append('key2', ary[1]); 17 form.append('key3', ary[2]); 18 form.append('file', fs.createReadStream(path)); 19 const result = await form.submit(URL); 20 21} 22// 対象のファイルパスの取得(listFilesの中身は省略してます) 23const pathList = listFiles(IKO_FOLDER_PATH); 24 25pathList.forEach( path => { 26 console.log('開始'+path); 27 uploadfnc(ary, path); 28 console.log('終了'+path); 29}, function(err) { 30 console.log(err); 31});

試しに5ファイル程で実行してみた結果が、
■バッチ側
※ファイル名が日付です。

開始:C:(パス)\key1\key2\key3\20190108.csv 終了:C:(パス)\key1\key2\key3\20190108.csv 開始:C:(パス)\key1\key2\key3\20190109.csv 終了:C:(パス)\key1\key2\key3\20190109.csv 開始:C:(パス)\key1\key2\key3\20190110.csv 終了:C:(パス)\key1\key2\key3\20190110.csv 開始:C:(パス)\key1\key2\key3\20190111.csv 終了:C:(パス)\key1\key2\key3\20190111.csv 開始:C:(パス)\key1\key2\key3\20190112.csv 終了:C:(パス)\key1\key2\key3\20190112.csv

開始と終了のログが間隔をあけずに出力されていて、レスポンスを待ってくれていないようです。

■サーバー側

処理開始:20190108.csv 処理開始:20190110.csv 処理開始:20190112.csv 処理開始:20190109.csv 処理開始:20190111.csv

ログの出力間隔を見ていると、ちゃんと前の処理が終わってから次の処理が行われていますが、処理順が送信順ではなくメチャクチャです。。。

試したこと

■バッチ側でsleepを入れてみる

javascript

1function sleep(time) { 2 const d1 = new Date(); 3 while (true) { 4 const d2 = new Date(); 5 if (d2 - d1 > time) { 6 return; 7 } 8 } 9} 10 11const pathList = listFiles(IKO_FOLDER_PATH); 12pathList.forEach( path => { 13 let ary = path.split("/"); 14 ary = ary.slice(-4); 15 console.log('開始'+path); 16 let form = new FormData(); 17 form.append('key1', ary[0]); 18 form.append('key2', ary[1]); 19 form.append('key3', ary[2]); 20 form.append('file', fs.createReadStream(path)); 21 22 form.submit(URL, function(err, res) { 23 console.log('終了'+path); 24 }); 25 26 sleep(10000); 27 28}, function(err) { 29 console.log("Receive err:" + err); 30});

結果のログはこうなってしまいました。(※3ファイルでテストしました)

開始:C:(パス)\key1\key2\key3\20190108.csv 開始:C:(パス)\key1\key2\key3\20190109.csv 開始:C:(パス)\key1\key2\key3\20190110.csv 終了:C:(パス)\key1\key2\key3\20190108.csv 終了:C:(パス)\key1\key2\key3\20190110.csv // ⇐9日と10日が逆に処理されている? 終了:C:(パス)\key1\key2\key3\20190109.csv

「開始:~」のログはちゃんと10秒間隔をあけて出力されていますが、その間サーバー側のCPUは上がらず、3行目が出た後で初めてサーバー側の処理が動き始めました。
「終了:~」のログも約5秒間隔(サーバーでの処理時間)に出ていて、私が期待する「リクエスト⇒レスポンス⇒リクエスト⇒レスポンス…」という処理順になってくれない上に、リクエストした順に処理してくれていないようです…。

補足情報(FW/ツールのバージョンなど)

■ローカル
Windows10
Node.js 10.15.1

■サーバー
CentOS 7
Apache 2.4
Node.js 10.15.1
(該当の処理はNuxt.js上のExpressで動いてます)

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

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

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

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

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

guest

回答2

0

ベストアンサー

uploadfuncにてawait form.submit(URL)としていますが、form-dataのREADMEを見る限りPromiseを返すようになっていないので、awaitする意味がなく、uploadfuncはアップロードリクエストが完了するまえにresolveすることになります。

せっかくpromise対応のaxiosをインストールしているようなので、form-dataと組み合わせて使うのが1つの手です。

js

1const uploadfnc = async (path) =>{ 2 ... 3 4 return axios.post('アップロード先URL', form, { 5 headers: form.getHeaders(), 6 }): 7}

参考リンク
POST request works in Browser but not on Node · Issue #1006 · axios/axios

また、pathList.forEach()の部分についても、uploadfuncの返り値をawaitしていないのでレスポンスを待っていません。for文でawaitする必要があります。

(pathListの各要素がfor-ofで取り出せると仮定)

js

1!(async () => { 2 for (const path of pathList) { 3 await uploadfunc(...); 4 } 5})();

最後に、while文で一定時間待つというのは普通JavaScriptではしません。

投稿2019/05/22 08:22

karamarimo

総合スコア2551

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

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

SystemAjisai

2019/05/22 09:44

ありがとうございます!! uploadfnc が非同期になってたんですね…! 自分のノートみたらちゃんと「async を付けたら非同期関数として定義される」と書いてました…。使わな過ぎて「await使う時につけなきゃダメなヤツ」に脳内変換されてました。 form-dataの件もありがとうございます。最初はUsageに書いてなかったからダメなのかなぁと思ってたのに、やってみたら意外とイケるかもしれない!の精神で色々試してたらどこがお試しで書いたコードかよくわからなくなってました。 sleepはやっぱダメでしたか・・・。 「javascript 一時停止」で検索したら、他の言語にはsleepと言う機能があるけどJavaScriptにはないと書いてあったので、たぶんダメなんだろうなぁと思いつつ、どうせ1回しか実行しないプログラムだからいいや!と思って試してしまいました。 karamarimoさんのおかげで無事に1個ファイルずつ送信することができました。 ありがとうございました。
karamarimo

2019/05/22 11:09

非同期であることに変わりはありません。async/await はあくまで非同期処理を同期処理っぽく書くための構文です。 今回の問題点は、uploadfncがresolveするタイミングが、リクエストのレスポンスを受け取るタイミングと一致していないという所でした。 単なるwhile-loopで待つべきではない理由は、JavaScriptがシングルスレッドであるためにwhile-loopが走っている間、他に何もコールバックなどが実行されないからです。代わりにsetTimeoutなどを使うべきです。 もっとも今の場合、レスポンスを受け取ったタイミングを await すればいいだけなので sleep などする必要もありませんが。
SystemAjisai

2019/05/23 00:56

while-loopはまさにJavaScriptがシングルスレッドだから他の処理が全部止まってくれることを期待して使ったんですが、そもそも私はシングルスレッドという用語も間違えて理解してるかもしれないです…。スレッドってCPU上の処理のまとまりだと思ってたので、今回のようにサーバーとローカルで別々のNode.js(JavaScript)が動いてたら、ローカルの処理をwhile-loopで止めてもサーバー側の処理は動くと思ってました。でも動作を見てる限りローカルでwhile-loopがグルグルしてる間、サーバー側の処理も動いてないんですよね・・・。form.submitした後で止めてるので、リクエストは飛んだ状態でローカルが止まり、サーバー側は何の影響もなく動くと思ったんですが…。 後学のため、教えて頂いたsetTimeoutも併せてどんな動きになるのか勉強してみます。 色々とありがとうございました。
karamarimo

2019/05/23 07:16

今の場合、リクエストを送りレスポンスを受け取ってからリクエストを送り...と同時に一つのことしかしていないですが、一般には複数の非同期処理が並行して行われ、コールバックもなるべく早く実行したいので while-loop で止めるのはアンチプラクティスになります。 > while-loopがグルグルしてる間、サーバー側の処理も動いてないんですよね これについてはソースコードを見ると原因がわかりました。 form-data の submit メソッドのソースコードを見ますと、リクエストを送る前にここで getLength という非同期の処理を行うメソッドを実行していることがわかります。 https://github.com/form-data/form-data/blob/017017813917aaeee928a50e6c7e5f3af5ab7c2f/lib/form_data.js#L428 getLength のコールバック関数でようやくリクエストが送られる、という形になっています。 しかし submit を実行したあとにすぐ while-loop でメインスレッドの実行が止まるため、今述べたコールバック関数がいつまでも呼ばれず、リクエストが送られない、ということだと思います。
SystemAjisai

2019/05/23 23:48

form-dataのソースまで調べて頂いてありがとうございます。 setTimeoutも試して動きが理解できました。 本来の目的だった過去データのアップロードも、先ほど無事に14台とも完了することができました。 本当に色々ありがとうございました。
guest

0

var benjo = ()=>{return new Promise((resolve, reject)=>{ console.log(1); resolve();//or reject(); });} var unchi = ()=>{return new Promise((resolve, reject)=>{ console.log(2); resolve();//or reject(); });} var mizujaaaa = ()=>{return new Promise((resolve, reject)=>{ console.log(3); resolve();//or reject(); });} benjo() .then(()=>{ unchi(); }).then(()=>{ mizujaaaa(); }).catch((e)=>{ })

コピペできるように書いたので適宜調べつつ改変してください。
ポイントはpromise

投稿2019/05/22 07:47

hentaiman

総合スコア6389

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

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

SystemAjisai

2019/05/22 09:31

ありがとうございました。 promiseの進化版がawaitだと思って、promiseはあんまり勉強してませんでした。 これを機に勉強してみます。
SystemAjisai

2019/05/23 01:01

解決した後に今更ですが…。 kuromarimoさんに回答頂いてプログラムを修正していて、やっとhentaimanさんの回答の意味がわかりました。 uploadfnc自体が非同期になってるのがすっかりヌケていたので、resolveしないといけないという認識すらなく「なんでpromise・・・?」となってました。 理解力無くてすみません・・・ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問