最近(と言っても一ヶ月前ですが・・・)node7.6.0がリリースされ、
--harmony-async-await
をつけなくても async/await
が利用可能となりました。
これにより、非同期処理を行うスクリプティングがより行いやすくなった(スクリプティング以上の用途ならばどうせ babel
を使うためそこまで影響はない)と感じています。
hubotなどのbotプログラミングも
定常タスクを楽にするためのスクリプティングの一種であり、用途上連携サービスのAPIをたくさん叩くため、
恩寵を存分に得ることができます。この記事ではhubotで async/await
を使う例を紹介します。
準備
手元の開発環境及び、botの動作環境のnodeを 7.6.0
以上にします。
僕は手元ではnodebrewを使っていますので、
nodebrew install-binary 7.6.0
nodebrew use 7.6.0
とします。
動作環境は、herokuやdokkuを使っている場合は
package.json
の engines
フィールドに 7.6.0
を指定してやるだけです。楽ちん。
{ "name": "myapp", "description": "a really cool app", "version": "1.0.0", "engines": { "node": "7.6.0" } }
余談ですが、個人のhubotや他のアプリケーションを気楽にデプロイする環境としてdokkuはとても便利です。 プライベートサーバーにインストールしておくととても捗ります。
コーディング
コーディングと言っても、特に工夫することはありません。 hubotのハンドラにおもむろにasync functionを渡すだけです。 hubotはasync functionに対応しているわけではないのですが、単にasync functionの終了を誰も待たない結果に 終わるだけです。もともとhubotはハンドラ内で非同期処理をバリバリ行う思想であるため、それで問題はありません。
さて、ここでは async/await
によって記述が楽になる例として、かんたんなスクリプトを書いてみます。
hubot-slackで指定した相手にDMを送る
slackのDMを指定したユーザーに送るだけのスクリプトです。
DMは API Methods | Slack の、
im.open
で送る相手をユーザーIDで指定してDMを開き、得られるチャンネルIDを chat.postMessage
することで送ることができます。
hubot-slackの4.0.0くらいから、 robot.adapter.client
経由でslackのAPIを叩くことができるようになりました
(参照)。
加えて、叩くことのできるAPIのメソッドは、コールバックを指定しない場合はPromiseを返します。
よって、async/await を使い、次のようにして引数のユーザーにDMを送るスクリプトが書けます。
robot.respond(/dm:send\s+([^\s]+)\s+(.+)/, async res => { const username = res.match[1]; const text = res.match[2]; const user = robot.adapter.client.rtm.dataStore.getUserByName(username); if (!user) { return; } const im = await robot.adapter.client.web.im.open(user.id); await robot.adapter.client.web.chat.postMessage(im.channel.id, text, { as_user: true }); res.send('ok!'); });
動かすと次のような感じです。
Github APIをたたき、指定したユーザーのそれぞれのpull request一覧を取得する
次の例は、githubのAPIを叩いて指定したユーザーのすべての所有リポジトリのpull requestを一覧するスクリプトです。
github APIの repos.getForUser
でユーザーのリポジトリ一覧を取得した後、それぞれのリポジトリに対して pullRequests.getAll
でプルリクエストを取得することで実現できます。
robot.respond(/github:repos\s+(.+)/, async res => { const username = res.match[1]; const repos = (await github.repos.getForUser({username})).data; for (const repo of repos.filter(repo => repo.open_issues_count > 0)) { const pulls = (await github.pullRequests.getAll({ owner: username, repo : repo.name, state: 'open' })).data; if (pulls.length > 0) { const text = pulls.map(pull => `:octocat: *${username}/${repo.name}* <${pull.html_url}|${pull.title}>` ).join('\n'); await robot.adapter.client.web.chat.postMessage(res.envelope.room, text, { as_user : true, unfurl_links: false }); } } });
動かすと次のような感じです。
※これらのスクリプトは要点だけを抜き出しているため、実際はエラー処理などがあったほうが望ましいです。 スクリプトの全体は、manaten/AsyncHubotExample に置いてあります。