この記事は Sansan Advent Calendar 2015の15日目です。
さぁ AdventCalender なんて書いてるので年末ですね。
最近フロントエンドをやらなかったので JavaScript 書かなくなって追いかけなくなったら、ずっと ES6 言われてたのがいつのまにか ES2015 っていう呼称が公式になってビビってますが、もう2016は目の前という事でさらに次の仕様である ES7 の機能が試せちゃう Babel を使ってみたいと思います。
Babelとは
もう結構有名なので知ってる人が大半ですが一応。
Bable は ES2015 や以降のセマンティクスで書かれているJavascriptを現在主流なブラウザでも使えるようにする変換処理ツールです。
さて今回本業は C# 書いているので C# 5.0 に影響を受け導入されそうな(現在 stage3 で candidate) Async/Await を試してみようかと思います。
準備
事前に以下はいれておきましょう。
環境 | version |
---|---|
node.js | v5.0.0 (多分4系でも動くはず) |
npm | 3.3.6 |
適当にディレクトリをきってその中で npm init
でプロジェクトを始めてからやります。
プロジェクト名やVersionなどの情報を聞かれるので適当に埋めて、
npm install --save-dev babel
でinstallできます。
(--save-dev
をつけるとpackage.jsonの devDependencies
で開発用のライブラリの依存関係を追加してくれます。)
{
"name": "async-await-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"babel": "^6.3.13"
}
}
これだけだとコード書くとき不便なんで gulp か grunt で watch して対象ファイルを保存したら自動でコンパイルできるようにしてみましょう。
今回は gulp を使ってみようかと思うので npm install --save-dev gulp gulp-babel
で install します。
install が完了したら下記のファイルを同ディレクトリに追加します。
var gulp = require('gulp');
var babel = require('gulp-babel');
gulp.task('babel', function(){
gulp.src(['src/*.js', 'src/**/*.js'])
.pipe(babel())
.pipe(gulp.dest('lib'));
});
gulp.task('watch', function(){
gulp.watch('./src/*.js', ['babel']);
});
gulp.task('default',['babel', 'watch']);
これで gulp
を実行すると src ディレクトリにある js ファイルを監視して lib ディレクトリに出力してくれるようになります。
ES7を試す前に preset を追加しておきます。
あとまだ polyfill も必要なのでそれも追加しておきます。
npm install --save-dev babel-preset-es2015 babel-preset-stage-3 babel-polyfill
さらに同ディレクトリに babel の設定ファイルである .babelrc を追加して presetを有効化します。
{
"presets": ["es2015", "stage-3"]
}
Async/Await の前に JavaScript と非同期の歴史
もともと JavaScript は非同期を callback で処理するため、複数の非同期処理を書くと callback でネストが深くなり callback hell 状態になりやすい言語でした。
func(function(data1){
func(function(data2){
func(function(data3){
// data1,2,3を使う処理
});
});
});
ネストが深くなりやすくいろいろと辛いので、それを解決するために jQuery の Deferred なんかも登場しました。
ちなみに node.js では Deferred ライブラリでは Q とかが有名らしいです。
var func = function(){
// deferred を用意
var d = new $.Deferred;
async(function(data){
// 非同期処理をしたら resolve に結果を渡す。
d.resolve(data);
});
// promise を返す
return d.promise();
};
$.when(func(), func(), func())
.done(function(data1, data2, data3){
// data1,2,3を使った処理
})
.fail(function(){
// error 処理
});
callback は減らす事ができましたが、やはり冗長な記述を繰り返す必要があり、通常の同期処理ほど直感的にかける形ではありません。
そんな中 generator 関数と yield 構文が ES2015 で採用されました。
これを利用して非同期処理を楽にかけるようにしたのが express や koa などの node.js の有名フレームワークの作者が作った co でした。
import co from 'co';
let func = () => {
return new Promise((resolve, reject) => {
async((data) => {
resolve(data);
});
});
};
co(function *(){
var data1 = yield func();
var data2 = yield func();
var data3 = yield func();
});
これにより見た目は非常に async/await に近い形までになりました。
co は 4.0.0 リリースの段階で async/await 実装までの飛び石として再定義されているらしいです。参考
Async/Await
というわけで長々語りましたが src ディレクトリをきってそこに早速 js ファイルを追加します。
ネタが思いつかなかったのでベタにTwitter使ってみました。
事前に npm install --save-dev node-twitter
しておきましょう。
import 'babel-polyfill'; // これがないと generator 関数が動かない
import Twitter from 'node-twitter';
let client = new Twitter.RestClient(
'CONSUMER_KEY',
'CONSUMER_SECRET',
'TOKEN',
'TOKEN_SECRET'
);
// Promise でくるんでやる
let getTweet = () => {
return new Promise((resolve, reject) => {
client.statusesHomeTimeline({},(error, result) => {
if (error){
console.log(error);
reject(error);
return;
}
resolve(result);
});
});
};
// async を generator の代わりに使う
(async function(){
// yield の代わりに await
let tweets = await getTweet();
console.log(tweets);
})();
これを gulp
で watch するか gulp babel
でコンパイルしましょう。
そうすると lib/async-await.js
が出力されます。
package.json の scripts に以下を追加すれば npm start
で実行できるようになります。
"scripts": {
"start": "node lib/async-test.js",
},
それか直で node lib/async-await.js
で実行してやりましょう。
ちゃんと async/await
できたっぽいです。
C#で書いたらこんな感じでぱっと見 async/await のシンタックスそっくりですね。
public async Task GetTweetAsync(Client client)
{
var result = await client.GetTweetAsync();
Console.WriteLine(result);
return Task.Result;
}
というわけで ES7 の Async/Await 触ってみました。
他にも ES7 は Decorator なんかも面白そうだと思っているので次回触ってみたいと思います。