Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8691

Node.jsの非同期処理でつまづいたところ

$
0
0

概要

以前の記事で、イベントループ方式の理解に苦しんだことについて記事にあげましたが、今回は、非同期処理についてつまづいたところを忘れないように記事にしてみようと思います。

つまづいた経緯としては、Node.jsでローカルサーバーを構築し、リクエストを受け付けられるようになったところで、「リクエストごとに処理を変えたいなぁ」、と思ったことが全ての始まりでした。

環境

自分が実際につまづいた時の環境は、以下となります。

  • OS:Windows10
  • ブラウザ:Chrome

やりたかったこと

  • データを取得して、それをページに表示する。

最初はMVCモデルに基づいてプログラムを組もうと考えていましたが、少しめんどくさくなりそうだったので、簡単にデータを取得・反映させるページを作ろうと思いました。

つまづいたところ

最初に記述したコードになります。

sample_node.js
// モジュールの取り込みconsthttp=require('http');constfs=require('fs');constpath=require('path');constejs=require('ejs');constmime=require('mime-types');constqs=require('querystring');constsetting=require('./setting');constdb=require('./db');// 本ファイルの親フォルダを取得(htmlファイル等を取得するため)constparentDir=path.dirname(__dirname);// webサーバーの作成constserver=http.createServer();// リクエストの受付を検知するserver.on('request',AppController);server.listen(setting.port,setting.host);console.log('server listeneing...');// URLのマッピング処理を振り分けるasyncfunctionAppController(req,res){letfilePath=req.url;if(filePath==='/favicon.ico'){res.end();return;}letparseData;if(req.method==="POST"){req.data="";req.on("data",function(chunk){req.data+=chunk;});req.on("end",function(){parseData=qs.parse(req.data);try{constresultData=selectAllItem();// // 結果を取得して色々処理...// buildPage(res,"/public/index.html",resultData);}catch(e){console.log(e.stack);buildPage(res,"err.html","");}});}elseif(req.method==="GET"){try{buildPage(res,"/public/index.html","");}catch(e){console.log(e.stack);buildPage(res,"err.html","");}}};// ページ遷移処理constbuildPage=function(res,filePath,postData){try{res.writeHead(200,{"Content-Type":mime.lookup(path.basename(filePath))});constcontent=fs.readFileSync(parentDir+filePath,'utf-8');constdata=ejs.render(content,{form:postData});res.write(data);res.end();}catch(e){console.log(e.stack);throwe;}}// DBに接続して、データを取得するconstselectAllItem=asyncfunction(){letitem1={};letitem2={};letitem3={};try{item1=db.selectItem1("*");item2=db.selectItem2("*");item3=db.selectItem3("*");}catch(e){console.log(e.stack);throwe;}return{"item1":item1,"item2":item2,"item3":item3};}

この状態で実行すると、selectAllItem()メソッド実行後の、resultDataに何も入っていませんでした。
これは、処理が非同期処理であるため、selectの実行をする前に、ページ遷移が先に実行されてしまったようです。
これを解決するためには、
 selectの実行 → データの編集処理 → ページ遷移
という順番を守ってもらわなければなりません。

解決策(Promise / async / await)

Node.jsでは、基本的にイベントループ方式のため、関数の終了を待ってはくれないみたいです。
そこで必要な知識となるのが、Promiseオブジェクトとasync/awaitの記述でした。
Promiseとasync/awaitは、書き方が違うだけで、実装できる内容はほぼほぼ同じです。
詳しい違いについては、以下の記事を参考にしてみてください。
https://qiita.com/h1guchi/items/0434f1295226cdd19a53

今回の解決策には、async / awaitを使用しました。
まずメイン処理から修正していきます。

メイン処理の修正

async_await_sample.js
// URLのマッピング処理を振り分けるasyncfunctionAppController(req,res){...//省略if(req.method==="POST"){req.data="";req.on("data",function(chunk){req.data+=chunk;});req.on("end",asyncfunction(){parseData=qs.parse(req.data);try{// 1・・・constresultData=awaitselectAllItem();// 2・・・// 結果を取得して色々処理...// // 3・・・buildPage(res,"/public/index.html",resultData);}catch(e){console.log(e.stack);buildPage(res,"err.html","");}});}elseif(req.method==="GET"){try{buildPage(res,"/public/index.html","");}catch(e){console.log(e.stack);buildPage(res,"err.html","");}}};

メイン処理で修正することは、下記の流れを守ってもらうことです。
 1. データを取得する
 2. データを編集する
 3. ページ遷移処理を実行する
非同期処理の場合は、この1・2・3が同時に実行されてしまったために問題が起きてしまいましたが、これを解決するために、awaitを使用します。awaitを実行したいasyncな処理の前に記述することで、その処理の終了を待つことができます。
ただし、このawaitを使用する場合は、その使用しているメソッドをasyncにする必要があるみたいです。(このために下層の関数はほとんどasyncをつけなくてはいけなくなりました。。。)

今回のメイン処理では、selectAllItem()メソッドの前に、awaitを記載しました。

次は、selectAllItemメソッド内の処理を修正していきます。

データ取得処理の修正

async_await_sample.js
constselectAllItem=asyncfunction(){letitem1;letitem2;letitem3;returnawaitPromise.all([db.selectItem1("*"),db.selectItem2("*"),db.selectItem3("*")]).then((values)=>{item1=values[0];item2=values[1];item3=values[2];return{"item1":item1,"item2":item2,"item3":item3};}).catch((err)=>{console.log(err.stack);throwerr;});}

ここでの処理は、Promise.all()という処理を使用しています。
最初修正した際は、1つ1つのselect処理にawaitをかけていたのですが、それぞれのselectの順番は気にしなくてもよいため、Promise.allとしています。
Promise.all()の中で定義されたasyncな関数は、全て非同期で実行されますが、その全ての関数の終了を待ってから次に進むため、一括でデータを取得したい時とかには向いているかもしれません。

まとめ

普段非同期処理などを意識することがあまりないため、かなり戸惑いましたが、とても勉強になりました。
普段はバックエンド側なので、javascriptを仕事で触ることは稀にしかないのですが、最近調べてみるとjavascriptでARの開発やサーバーの構築やフレームワーク使って簡単にリッチなWEBページを作ったり。。。すごいですねぇ
これから勉強して、またここにアウトプットしていこうと思います。

補足(参考リンク)

Promiseとasync/await
https://qiita.com/suin/items/97041d3e0691c12f4974
https://qiita.com/toshihirock/items/e49b66f8685a8510bd76#comments


Viewing all articles
Browse latest Browse all 8691

Trending Articles