coffeescriptとstep.js でどれぐらい非同期を同期的に簡潔に書けるか?
なんか日本語がおかしいですが…
nodejs/expressの習作として、簡単なマイクロブログ作ってたんですが
MongoDBのORMとしてMongooseを使ってて、DBの呼び出しってNodeJSでは基本的に非同期なので
たしかにnodeの設計思想からしてそうすべきだとは思うんですが、単純にコードとしての見栄えが悪くなってました
で、js直には触らず、全部 coffeescript で書いてるんですが
たとえば、expressとmongooseに突っ込むところを組み合わせると、こうなります
require 'coffee-script' mongoose = require 'mongoose' express = require 'express' OAuth = require('oauth').OAuth # 中略 # OAuht認証のところだけ抜粋 app.get '/oauth_verify',(req, res)-> oauth_token = req.query.oauth_token oauth_verifier = req.query.oauth_verifier if oauth_token and oauth_verifier and req.session.oauth oauth.getOAuthAccessToken oauth_token, null, oauth_verifier, (error, oauth_access_token, oauth_access_token_secret, results) -> req.session.regenerate ()-> req.session.name = results.screen_name req.session.twitter_id = results.user_id User.findOne { id:results.user_id } , (err,user)-> if not user item = new User() item.name = results.screen_name item.id = results.user_id item.twitter = token: oauth_access_token token_secret: oauth_access_token_secret item.save (err)-> console.log 'add new user:'+JSON.stringify(item) console.log "[login] #{results.screen_name}" if user res.redirect '/' else oauth.getOAuthRequestToken (error, oauth_token, oauth_token_secret, results)-> req.session.oauth = oauth_token: oauth_token oauth_token_secret: oauth_token_secret request_token_results: results res.redirect 'https://api.twitter.com/oauth/authorize?oauth_token=' + oauth_token
node.js+socket.io+oauth+SessionWebSocketでログイン付きチャットを作るメモ - すぎゃーんメモを参考に、coffeescriptで書きました
コード全体は express/coffeescript ― Gist に置いておきます
どうでしょう、expressとmongooseのコールバックで奥へ奥へと… まあ悪かないんですが、プログラマたるもの、単純にネストが深いコードは警戒してしまうわけです
まあ、元々簡単に書けてしまう部分でもないんですが、だからこそシンプルにしておきたい、ですよね
っていうので、そういえば非同期を上手く記述するライブラリがあるよなーと、色々触ってみたんですが、
一番シンプルそうだった step.jsってのに落ち着きました。
step.js
creationix/step - GitHub
インストールは npm で npm install step
やってることは単純で、コールバック関数に this を代入してやると、次の関数呼び出しではそこから処理が開始されます。
公式のサンプルコードはこんな感じです
Step( function readSelf() { fs.readFile(__filename, this); }, function capitalize(err, text) { if (err) throw err; return text.toUpperCase(); }, function showIt(err, newText) { if (err) throw err; console.log(newText); } );
便利なstep.jsなんですがcoffeeescriptと組み合わせるときはちょっと工夫がいります
coffeescriptは、関数の中では「最後に評価されたものをreturnする」って仕様になってて、
これはこれでメソッドチェーンとか書きやすくはなるんですが、step.jsではvoid以外でreturnされると、そこで挙動が変わってしまいます
だから、明示的に空のreturnを書いてやる必要があります。ちょっとカッコ悪いですね。
という感じで、OAuthのコードを描き直してやるとこうなります
app.get '/oauth_verify',(req, res)-> oauth_token = req.query.oauth_token oauth_verifier = req.query.oauth_verifier if oauth_token and oauth_verifier and req.session.oauth _atoken = _asec = "" _res = {} step ()-> oauth.getOAuthAccessToken oauth_token, null, oauth_verifier,@ return , (e, oauth_access_token, oauth_access_token_secret, results) -> _atoken = oauth_access_token _asec = oauth_access_token_secret _res = results req.session.regenerate @ return , ()-> User.findOne { id:_res.user_id } ,@ return , (e,user)-> console.log user if not user item = new User() item.name = _res.screen_name item.id = _res.user_id item.twitter = token: _atoken token_secret: _asec item.save (e)-> console.log 'add new user:'+JSON.stringify(item) req.session.name = _res.screen_name req.session.twitter_id = _res.user_id console.log "[login] #{_res.screen_name}" res.redirect '/' return else oauth.getOAuthRequestToken (error, oauth_token, oauth_token_secret, results)-> req.session.oauth = oauth_token: oauth_token oauth_token_secret: oauth_token_secret request_token_results: results res.redirect 'https://api.twitter.com/oauth/authorize?oauth_token=' + oauth_token
ネストは減りましたが、縦に長くなってしまいました。
あと、step.jsで実行されるそれぞれの名前空間は、step関数が呼び出された時点に依存していて、関数の中の名前空間は、そのクロージャで閉じているので、
関数から関数へ値を渡したいときは、バッファーとなる変数を用意しとく必要がありました。_atoken,_asecret,_res がそれです
結論として
- ちょっとネストが深くなるのは許容する
- ネストが3段超えた辺りでstep.js使うのを検討する
っていう風にすると可読性が高くなるんじゃないかなって思いました。まる。
他の非同期ライブラリとかは、InfoQ: 仮想パネル: JavaScriptで非同期プログラミングを乗り切る方法が、それぞれの設計思想とかまとまって良かったです。