0

mochaのitとifを間違えるのでESLintのrule pluginを自作した

mochaでtestを書く時にitという関数を使うのだが、ifと書き間違えている事に気づかず30分ぐらいハマった。前にも同じ事があったのに30分かかったので、この調子だと何度でもハマる自信がある。


itがifでも文法エラーが起こらないし、そのテストコードの部分が実行されないだけなので気づかない。
シンタックスハイライトされてても文字の色で判別なんて人間には不可能。
間違え探しみたいなデバッグをしたくないからテスト書くのに、そのテスト自体が間違え探しなのはどうなのと思う。

もっと細かく考えると

  • if文に定数を渡せるのがおかしい
  • カンマ演算子の存在自体が悪い
  • itって関数名どうなの
  • そもそもtestの中でif文なんて必要ない

というのがある。


eslint-if-in-test作った

テストコードの中でif文を使う理由が思いつかないので、ESLintでなんとかする事にした。ESLintはプラグインを簡単に自作できるのですぐできた。

https://www.npmjs.com/package/eslint-plugin-if-in-test

describe()の中でif文を使っていたら警告してくれる


他のソリューション

eslint-if-in-test作っている時にESLint自体のテストがうまく書けなくてtwitterで愚痴っていたら色々教えてもらえた。

ESLintには既に色々プラグインがある
特にno-constant-conditionはif文に定数渡したら警告してくれるので良いなと思った。

というかこっちで十分だと思う。完成してから知った。


ESLintのruleを作る

ESLintのruleの作り方を調べたのでメモしておく。かなり簡単に作れる。

まずドキュメントを読んでいくとruleの作り方というのがあるんだけど、ESLintのソースコードの中のlibの下にファイル作ってねとか言われる。
pluginの作り方の方を見たほうが良さそう。


ESLintのpluginを作る

pluginの中にruleをまとめて、npmとして公開できる。
yeomanにテンプレートがある。

% mkdir eslint-plugin-if-in-test
% cd eslint-plugin-if-in-test/
% npm i yo generator-eslint -g
% yo eslint:plugin
(質問に答える)
% npm i

テンプレができる

最終的にこうなった。 lib/rules/if.js と tests/lib/rules/test_if.js を自分で書いた。
├── README.md
├── lib
│   ├── index.js
│   └── rules
│   └── if.js
├── package.json
└── tests
└── lib
└── rules
└── test_if.js

罠もあった。
作られたテンプレのpackage.jsonで指定されているESLintが1.2.0と古い物なので最新(1.10)に直した。

あとlib/index.jsのrequireindex npmの呼び出し方が間違ってて動かないので自分でrequireした

この辺は後でプルリクしたい。


pluginにRuleを書く

ESLintはJavaScriptを解析してAST(抽象構文木)を作って、それを解析して警告などを出す。
ASTのnodeのtypeをruleで指定しておくとそれを受け取れるので、適当に解析してエラーがあったらreportを返せばいい。

ASTはAST explorerでもみれる。


とりあえず対象ファイルがtestディレクトリの中にあるかをcontextで確認して、if文があったらcontext.reportするようにした。
lib/rules/if.js
var path = require("path");
var chalk = require("chalk");

module.exports = function(context){

// テストコードのディレクトリ内だけ判定
var testDir = path.resolve(context.options[0].directory);
var testDirPattern = new RegExp("^" + testDir + "/");
if(!testDirPattern.test(context.getFilename())) return {};

// "if" statement in test-code
return {
"IfStatement": function(node){
var code = context.getSourceCode().getText(node).slice(0, 15) + "~~~";
context.report({
node: node,
message: code.replace(/^if/, chalk.bold.underline('if'))
+ " is probably typo of "
+ code.replace(/^if/, chalk.bold.underline('it'))
});
}
}
};

// .eslintのバリデータ JSON-Schema
module.exports.schema = [
{
type: "object",
properties: {
directory: { // directoryプロパティがstring型で必須
type: "string"
}
},
additionalProperties: false
}
];

ESLintが出すnodeの種類はestree/spec.mdにリストがある。

実装サイズの小さそうなpluginをnpmjs.comで探して参考にもした。
lodashをrequireする時にlodash全体を読み込んでいたら警告するのとか
https://github.com/eslint-plugins/eslint-plugin-lodash/blob/master/src/rules/import.js

console.logを使っていたら警告とか
https://github.com/joeybaker/eslint-plugin-no-console-log/blob/master/lib/rules/no-console-log.js


pluginを動かす

作ったpluginが動くかどうかは、mochaでテスト書いてる適当なアプリの中にインストールすればいい。
開発中のnpmを読み込む場合はnode_modules/内にシンボリックリンク貼るといい。
% cd node_modules/
% ln -s ../../eslint-if-in-test eslint-if-in-test
たぶんnpm linkを使っても良さそうだけどやったことない。


.eslintrc に設定する。module.exports.schemaで指定したオプションで、test/をdirectoryとして渡す。
{
"plugins":[
"if-in-test"
],
"rules":{
"if-in-test/if": [1, {"directory": "test"}]
}
}

if-in-testのルールを読み込んでlintできる。
% eslint test/*.js



describeの中のif文のみ警告する


もっとまともに書く。itはdescribeの中でしか使わない。

ESLintのルールを自作しよう! – Yahoo! JAPAN Tech Blog
を読んでいたら、node typeの末尾に:exitを付けたらtree walkして出る時に拾える事を知った。

nodeへの出入りをカウントする事でとても簡単にifがdescribeの中にあるかどうか判定できた。
lib/rules/if.js
var path = require("path");
var chalk = require("chalk");

module.exports = function(context){

// テストコードのディレクトリ内だけ判定
var testDir = path.resolve(context.options[0].directory);
var testDirPattern = new RegExp("^" + testDir + "/");
if(!testDirPattern.test(context.getFilename())) return {};

var describeCallCount = 0; // describe呼び出しの中にいるかカウントする
return {
"CallExpression": function(node){ // 何かの関数呼び出しに入った
if(node.callee.name === "describe") describeCallCount += 1;
},
"CallExpression:exit": function(node){ // 出た
if(node.callee.name === "describe") describeCallCount -= 1;
},
"IfStatement": function(node){
if(describeCallCount < 1) return;

// describe()の中の時
var code = context.getSourceCode().getText(node).slice(0, 15) + "~~~";
context.report({
node: node,
message: code.replace(/^if/, chalk.bold.underline('if'))
+ " is probably typo of "
+ code.replace(/^if/, chalk.bold.underline('it'))
});
}
}
};

// validator for .eslint
module.exports.schema = [
{
type: "object",
properties: {
directory: {
type: "string"
}
},
additionalProperties: false
}
];


最初はnode.parentを辿って確認してたんだけど、カウントする方が楽だったのでやめた。


ESLint Ruleのtestを書く


ルールのテストも書ける。

http://eslint.org/docs/developer-guide/working-with-plugins#testing
http://eslint.org/docs/developer-guide/working-with-rules#rule-unit-tests

ESLint組み込みのRuleTesterにvalidとinvalidを渡す
tests/lib/rules/test_if.js
var path = require("path");

var rule = require("../../../lib/rules/if")
var RuleTester = require("eslint").RuleTester;

var ruleTester = new RuleTester();
ruleTester.run("rule \"if\"", rule, {
valid: [
{
code: "if(true){ }",
options: [{directory: "test"}],
filename: path.resolve("test/foo/bar/baz.js")
},
],
invalid: [
{
code: "describe('foo', function(){ if('should ~~', function(){ }); }); // \"if\" in describe",
options: [{directory: "test"}],
errors: [{message: null, type: "IfStatement"}],
filename: path.resolve("test/foo/bar/baz.js")
}
]
});

mochaでテストできる
% mocha tests/lib/rules/test_*.js


Testerにfilenameを渡せる事を教えてもらえてテストが実現できた。

0

新横浜Twitter botをAWS Lambdaとcoで作った

新横浜は新幹線が止まるしイベント施設が複数あるので、土日はなんらかの目的をもって訪れる人が多いみたいでニコニコ笑顔の人が多くて良い。そのかわりイベントの入場前・退場後の時間帯は強烈に混雑して駅に入れなくなる事がまれによくある。
そこでtwitter botにイベント情報を喋らせることにした。毎朝ツイートするので危険を察知できる。
そのうち混雑度を算出する機能も追加したい。あと開場時間も。


ソースコード
https://github.com/shokai/neoyokohama-bot

coを使うと、非同期処理を同期的に書いて順番に実行したり、あるいはasync.jsみたいに同時実行して完了を待ち受けて合流させたりがコールバック地獄を起こさず簡単に書けるので、こういうバッチ処理的な順序が大事な処理は書きやすいだろうなと思って色々試していたらいつの間にかtwitter botになってた。

まず横浜アリーナと日産スタジアムの予定を同時に取得し、Tweetする。Tweetしつつ同時に天気予報を取得する。先のTweetのstatus_idをin_reply_to_status_idにセットしつつ天気予報をTweetする事で連続tweetになる。この間約3秒である。

スクリプトはAWS Lambdaで毎朝1回実行している。AWS Lambdaはメモリ使用量×処理時間で課金されるが、毎日約48MB×3秒で月に4.3GBなので、無料枠の400000GBに十分収まっている。同じようなbotをあと10万個ぐらい無料でデプロイできる。

Lambdaについてぐぐると、最近はPython使ってる記事が多いというかほぼJavaScriptで書いてる記事が無いような?状況だけど、たぶんコールバック地獄がつらいからLambdaに向いてないとか思われてそう。
coでだいたい解決するし、それどころか複数のIO待ちする処理を同時実行する等も書きやすいからJavaScriptいいと思う。

以下、coやLambdaについて忘れないようにメモしておく

async/awaitで非同期処理を同期的に書く

まず最初に、12月半ばごろ、esnext – Async Functions – Qiitaを見て、非同期処理を同期的に書けるasync/awaitという文法が作られているのだなあと思って調べていた
function foo(wait = 1000){
return new Promise((resolve) => {
console.log(`wait ${wait} msec`);
setTimeout( () => {
resolve(`done waiting ${wait} msec`);
}, wait);
});
}

(async function(){
const result = await foo(1000); // コールバックじゃなくて同期的に呼び出せる
console.log(result);
})();
babelでトランスパイルするとGeneratorを使ってwhileで回して待ち続ける感じになっているみたいだけどよくわからなかった。regenerator-runtimeが使われている。


coで非同期処理を同期的に書く

そういえばGeneratorで同期的に書けるやつっていうと、coを使ったことなかったな、と思いだして触りだした。
coもまたPromiseを返す関数を同期的に扱える。
import co from "co"

co(function *(){
const result = yield foo(1000); // 上と同じ関数fooを同期的に呼び出す
console.log(result);
});
async/awaitと似たような感じになる。


coの方はもっと高機能で、Generatorを包んだco自体がPromiseを返すのでさらに別のcoに食わせたりできる。coを使って書いた関数をライブラリとして提供して、それをmainのcoから呼び出して・・・というのを何重もできる。
co.wrap(generator)すると即時実行せずに関数として定義できるのでcallにthisを渡せばES6のclass構文にうまくハマる。

src/yokohama-arena.es6
// 横浜アリーナのイベント情報class
class YokohamaArena{

constructor(){
this.url = "http://www.yokohama-arena.co.jp/event/";
}

getEvents(){ // 全イベント取得
return co.wrap(function *(){
const html = yield this.getHtml(); // 非同期でhtmlを取得
const events = this.parseHtml(html); // parseは同期処理
return events;
}).call(this);
}

getMajorEvents(){ // 設営日を除いたイベント取得
return co.wrap(function *(){
const events = yield this.getEvents();
return events.filter((i) => { return !(/設営日/.test(i.title)) });
}).call(this);
}

getHtml(){
return new Promise((resolve, reject) => {
superagent
.get(this.url)
// (略)


coで複数の非同期処理を同時実行する

上のようにイベント施設ごとのスクレイピングモジュールをそれぞれ作っておいて、coで同時に呼び出す。
yieldにオブジェクトを渡すと全部同時に評価して、完了したら結果をまとめて返してくれる。下の例だとevents.arenaで横浜アリーナのイベント情報が、events.nissanで日産スタジアムの情報が得られる。

src/main.es6
  co(function *(){
const events = yield {
arena: arena.getMajorEvents(),
nissan: nissan.getMajorEvents()
};
});


同時実行はyieldに配列を渡してもよい。
ES6の分割代入を使ったら、tweetを投稿しつつ同時に天気予報を取得するのが綺麗に書けた。
    const [tweet, forecast] = yield [
twitterClient.update({status: tweetText}),
weather.getForecast()
];


coのエラー処理

co自体がPromiseを返すので、まとめてcatchできる。
co(function *(){
/** なんかエラーが起こりうる処理 **/
}).catch((err) => {
console.error(err.stack || err); // スタックトレースがあればprintする。無ければエラーそのものを
});

Node.jsでAWS Lambdaを書く

AWS Lambdaはアプリケーションのプロセスではなくlambda、つまり関数単位でホスティングしてくれるサービス。AWSにあるDBやqueueなどのサービスにデータが書き込まれたらそれに反応してLambda Functionが起動して小さな仕事をしてすぐ終了するような感じで使われる。functionの起動さえ十分に速ければ、確かにずっとプロセスが起きていてメモリを専有しているような普通の実装よりも効率がいい。

ためしにfunctionを作ってみるとわかりやすい。新規作成するとblueprint(ある程度できてるサンプルコード)から選択させられるので、node-execを選ぶと一番シンプルでいい。
child_processでshellコマンドをexecするサンプルになっている。

eventはevent.cmdを参照してchild_process.execするのに使われているが、これはActionsのドロップダウンメニューからJSON形式で編集できる。
contextにはawsRequestIdやinvokeidが入っていて、これでAWS上のどういう環境で実行されているかが見れる。ローカルマシンで実行しているかとか判別するのにも使える。

Configurationタブを見ると、index.jsというファイル名とhandlerという関数名が指定されている。
基本形はmodule.exports.handlerを宣言したindex.jsというファイル1つで、これがAWS上でrequireされて呼び出される。
module.exports.handler = function(event, context){
/** なんか処理 **/
context.done(null, "完了");
}
context.doneかcontext.failを呼ばないと設定したtimeout時間まで延々動き続けて課金されるので注意する。

co自身から出るPromiseでエラーをcatchした時も、context.failさせる。


AWS Lambdaでnpmを使う

ファイル単一でformに貼り付けて実行するのではなく、zipで固めてアップロードでもいい。
node_modules/ 以下も一緒にzipすればいい。最大50MBまでいける。

Using Packages and Native nodejs Modules in AWS Lambda | AWS Compute Blog
Lambda上でネイティブのOpenCV拡張を呼ぶために、EC2インスタンス作ってOpenCVをビルドして静的リンクして全部zipで固めている例もある。

このへんはserverlessを使えば全部CLIでできるみたいだけど、他のAWSの機能を使う予定が無いし生のAWS Lambdaを触ってみたかったので今回はzipでアップロードでやる事にした。


AWS LambdaでES6を使う

AWS Lambdaのnodeはv0.10.36なので、ちょっと古い。
ES6(ES2015)を実行するにはbabelで事前にコンパイルしておく必要がある。babel-registerで逐次実行しようとしたらsyntax errorになった。もちろんローカルのnode 0.10では動いているんだけどどうして動かないのかわからない。require-hookがLambdaでは効かないようになっているのかもしれない。

事前にsrc/以下をbabelでES5に変換してからzipで固めた。
% babel src/ --out-dir dist/ --source-maps inline
% zip -r bundle.zip index.js .env dist/ node_modules/
node_modules/ の中に実行に必要ないモジュールもたくさん混じってしまっているが、面倒なので全部まとめてbundle.zipに固めてしまった。npmが436個もあるけど11MB程度におさまったのでまあいいや。

ローカルで実験する

このtwitter botはAWSの機能は何も使っていないので、ローカルでも動く。

AWS Lambdaで実行された時はindex.jsがrequireされてそこからexportされてるhandlerメソッドが呼ばれる。
ローカルでも同じような動作をするrun.jsを用意して、そこから実行した。
run.js
var index = require("./index");
index.handler();

テスト

CircleCIでやってる
https://circleci.com/gh/shokai/neoyokohama-bot
ESLintかけてからmochaでクローラ等のモジュール毎のテストもやって、
一応buildしてzip作ってdry runまで
test:
override:
- npm run test
- npm run build
- npm run zip
- DRY=true npm start

AWS Lambdaに環境変数を渡す

twitterへの投稿にはtwitter npmを使った。
OAuthのaccess tokenが必要だが、こういうのはgitリポジトリにコミットしたくないので環境変数で渡したい。

でもAWS Lambdaには環境変数を渡す機能がないので、dotenv npmを使った。dotenvは.envファイルに書いた環境変数をloadできるので、.envファイルをzipに含めるようにした。
serverlessもdotenv使ってる。

require("dotenv").load({silent: true});
import Twitter from "twitter";

const client = new Twitter({
consumer_key: process.env.CONSUMER_KEY,
consumer_secret: process.env.CONSUMER_SECRET,
access_token_key: process.env.ACCESS_TOKEN_KEY,
access_token_secret: process.env.ACCESS_TOKEN_SECRET
});
なおdotenvはloadしたファイルの中でしかprocess.envが上書きされないっぽい。プログラムの頭でやっても効果ない。


AWS Lambdaを定期的に実行する

ここまででだいたいスクリプトができたので、あとは毎朝実行するように設定する。
event sourceにscheduled eventを追加してcrontabのような記法で書く。
タイムゾーンがUTCなので、イギリスだから日本から9時間時差がある。毎日23時(日本の8時)に実行するようにした。

scheduled eventは英語版のドキュメントにのみ説明されている。日本語ドキュメントだと項目丸ごと無いのでしばらく困惑した。
Using AWS Lambda with Scheduled Events – AWS Lambda

crontabっぽいけど末尾にyearがある。

cron(Minutes Hours Day-of-month Month Day-of-week Year)



タイムゾーンを日本に設定する

スクレイピングしてきたイベントのリストから今日開催されるものを判定して抜き出す必要があるので、タイムゾーンを日本時間に設定したほうが計算しやすい。
Herokuでは環境変数TZを上書きしてやってたけどAWS Lambdaには環境変数を設定する機能が無い・・・ので、index.jsの冒頭で設定してしまう事にした。
process.env.TZ = "Asia/Tokyo";
これ以降はDateがちゃんと比較できる
Date.prototype.isToday = function(){
const today = new Date();
return this.getYear() === today.getYear() &&
this.getMonth() === today.getMonth() &&
this.getDate() === today.getDate();
};


やっぱserverless使おうかな

気温もtweetさせたいんだけど、前日との差をtweetしたほうがいいのでSimpleDBか何かに書き込みたい。なのでやっぱりserverless使ったほうが良さそうな気がしている。

あと、in_reply_to_status_idを使って連続tweetしてるんだけど処理が速すぎてtwitterに認識してもらえない。
間に数秒ディレイを入れたら認識されたけど、それではlambdaっぽくないので2つのlambda functionに分けてscheudled eventも1分ずらしてDBで最後のstatus_idを受け渡すかー、それならなおのことserverless使ったほうが良さそうだなとか思った。イベントソースが複数あってそれをGUIでポチポチやっていくのは混乱しそうなので全部コードでやりたい。

混雑度予想と開場時間もほしい。

0

JavaScript情報の収集

LDRでazuさんのはてブを見たりもしているんだけど、自分JavaScriptガチ勢ではないのと流れが速いのとで追い切れなかったりする。

最近はお風呂で湯船につかっている時にタブレットでmenthasを見てる。
http://menthas.com/javascript

どういうアルゴリズムなのか詳細はよくわからないけど、わりと面白い記事が流れてきてしかもノイズが少ない。内容面白いのに3,4人しかbookmarkしてない記事も流れてきて掘り出し物感もある。

LDRをタブレットで快適に見る方法があればまた違う気もするけど、良い方法が無い

あとmenthasにはRSSもあるのでそっちもLDRに登録して見てる http://menthas.com/javascript/rss

0

ES6の勉強をした

Web DB Press vol.87のES6の特集を読みながら、ES6を勉強した。とてもわかりやすかった。
紹介されている機能をちょこちょこと試す細かいスクリプトを色々書いたりメモを取ったりしたのでまとめておく。色々な機能があった。

https://github.com/shokai/es6-study

最近Web MIDI APIを使ってたんだけど、そのインタフェースがES6のPromiseとfor-ofで回す新しいIteratorとTypedArrayで実装されていたので、今後ブラウザに増える新しいAPIがES6前提で作られるなら普段からES6で書くようにしたほうがいいかなと思った。

個人的にはcoffeeの方がES6よりちょっと好き。でもfor-of文がES6で新しく増えたMapやIteratorを使う時に重要なんだけど、coffeeのfor-of文は別の意味があるので、coffeeからES6のAPIを使うのは辛そうな気がしている。一応IteratorはforEachで回せるけど


ES6の機能メモ

(coffeeとの比較みたいな感想入り)

Promise

非同期処理の新しいインタフェース。ScalaのFutureみたいなやつ。

変数の初期化

var foo = "zanmai";
var bar = "kazusuke";
var obj = {foo, bar};
// オブジェクト初期化省略記法
// {foo: "zanmai", bar: "kazusuke"} というオブジェクトができる

分割代入

var [foo, bar] = ["zanmai", "kazusuke"]; // 配列を配列で受けるとそれぞれ変数に代入できる
[a, b] = [b, a]; // 入れ替えもできる

class構文がある

extendsとかもできる。superで呼び出す
ほぼcoffeeのclassと同じ感じ
static/setter/getterを設定する修飾子がある
 class User extends EventEmitter2{
constructor(name){
super({delimiter: ":", wildcard: true}); // superで継承元を呼び出す
this.name = name; // プロパティを持てる
}
get website(){ // getter設定
return "http://github.com/"+this.name;
}
}
組み込みクラス(Array等)も継承で拡張できる
これまでArrayなどにメソッドを生やしたりすると全体に影響が及んでいたのが解決できる

Arrow Function

 var sum = (a, b) => {
return a + b;
}
functionを=>で書ける
外側のthisが引き継がれる
coffeeの=>と同じ
thisを引っ張ってきたくなければ今まで通りfunctionを使う


Generatorがある

function()*{ yield foo; }
値を複数回返せる機構
returnではなくyieldで返す
Rubyのblock+yieldと同じ

requireのかわりにexport/import

requireの挙動が複雑だったので仕様を直したもの
babelではrequireに変換される
export function foo(){ ~~~~ };
// module.exports.foo = function(){ ~~~ }; と同じ

export default function(){ ~~~ };
// defaultを付けると、module.exports = function(){ ~~~ }; と同じ

import foo from "foo";
// var foo = require("foo"); と同じ

import {EventEmitter2} from "eventemitter2";
// 部分的にrequireできる。var EventEmitter2 = require("eventemitter2").EventEmitter2; と同じ
// coffeeでrequireの左辺に{ }付けた時と同じ挙動

import {aaa, bbb} from "foo";
// 複数の子をまとめてrequireできる
// var aaa = require('foo').aaa;
// var bbb = require('foo').bbb; と同じ
classを1つだけ提供する時は
export default class ClassName { ~~ } で提供し
import className from “ClassName”; で読み込む

ユーティリティ関数を複数提供する時は、exportとimport {name}で部分的に読み込めるようにすると良さそう?

色々と関数が追加されている

String.repeat(num); // 文字列をくり返す
Array.from(obj); // 新規Arrayインスタンス作成
Object.assign(target, source1, source2...); // targetにsourceのプロパティをmix-inする

テンプレート文字列がある

`my name is ${this.name}`
バッククオートで囲う
${}の中が評価される

タグ付きテンプレート文字列

例えば
gyazz`gyazz記法を[[展開]]できる`
と書くと、 gyazz記法を<a href=”./展開”>展開</a>できる
function gyazz(templates, …values){ 処理して文字列を返す }
のような関数を宣言しておけば使える
`の前に関数名を書いて変換器を指定する
function gyazz(templates, ...values){
console.log("---gyazz markup start---");
var reg = /\[{2}([^\[\]]+)\]{2}/g;
var str = "";
for(var i = 0; i < templates.length; i++){
str += templates[i] || "";
str += values[i] || "";
}
if(!reg.test(str)){
return str;
}
return str.replace(reg, "<a href=\"./$1\">$1</a>");
}

2/8/16進数

0b1010
0xFA
0o70
接頭辞で書ける

デフォルトパラメータ

 var foo = function(name="shokai"){ console.log(name); };
foo("hoge"); // => hoge
foo(); // => shokai
引数なしの時の初期値を設定できる

可変長引数

var foo = function(...bar){ (略) }
barをArrayとして受け取れる
変数名の前に...を付ける
es5では特殊変数argumentsを見なければできなかった
関数宣言時に書けるのでぱっと見わかりやすい
静的解析ツールの支援も受けやすそう

スプレッドオペレータ

[1,2, ...[5,6,7]] // [1,2,5,6,7] になる

// Underscoreのrangeと組み合わせると強い
[..._.range(1,5), 11, 12, ..._.range(20,23)] // [1,2,3,4,5,11,12,20,21,22,23] になる

var [a,b,...c] = [1,2,3,4,5,6,7]; // 分割代入と組み合わせ
// a => 1
// b => 2
// c => [3,4,5,6,7]

定数

 const NAME = "shokai";
NAME = "zanmai"; // 代入できない
console.log(NAME); // => shokai

ブロックスコープ変数

letで宣言する
 if(true){
let name = "shokai";
console.log(name); //=> shokai
}
console.log(name); // name is not defined
for(let i = 0; i < 10; i++){ // varで宣言すると全部10になる
setTimeout(function(){
console.log(i); // 1,2,3,4,5,6,7,8,9,10
});
}
letがあるので、coffeeのdo (args) -> が必要なくなる


イテレータ

for(let i of arr){ console.log(i); }
// MapやSetはkey, valueで回せる
for(let [k,v] of map){ }

TypedArray

型付き配列
var arr = new Uint8Array(8); // 8bit型
arr[0] = 256; // これは8bit超えてるので代入されない。例外は発生せず代入処理だけが実行されない
arr[1] = 255; // 代入できる
WebGLやWebMIDIなど大量のデータを処理する時に速い
Nodeのbuffer由来
length等いくつかのプロパティが無い
Arrayに変換すればok
var arr2 = Array.prototype.slice.call(arr);

Proxy

Rubyのmethod missingと同等の事ができる
現状Firefoxでしか動かない?


実行環境・ツール


ES6をそのまま実行できる環境


ローカルで実行するちょっとしたスクリプトとかだと、いちいちコンパイルせずにES6のまま実行できると良い

Chrome
だいたいそのままes6が動く
Arrow Function以外
Chrome拡張とか作るならいきなりES6で書いても良さげ

Firefox
だいたいそのままes6が動く
Proxyもたしか動くはず
classが無い

Node.js
0.12なら--harmonyオプションを付けるとes6が実行できる
rest parameterやspread operatorが使えない

iojs
class, Generatorなど多くの機能をサポートしてる
--harmonyオプションを付ければもっと色々使える
--harmony_rest_parametersや--harmony_arrow_functionsなども必要に応じてONにできる
サーバーで実行するならiojsで良さそう。herokuでもengine指定すれば使える

babel/register
babelに含まれているpolyfill
ブラウザでもサーバーでも動くのでわりと良さそう
プログラムの頭で
require("babel/register")();
しておくと、以後ES6コードをrequireするとふつうに実行できる
Nodeのrequire関数がフックされて拡張されるらしい
最初のregisterするファイルにはes6を書けない、syntax errorになる
Node0.10でもfor-of文が動かせる

babel-node
babel付属の実行コマンド
es6を直接実行できる
Node0.10でもfor-of文が動かせる

osascript
MacのAppleScript実行コマンドだったが、YosemiteでJavaScriptでも書けるようになった。ES6が一部サポートされている。
分割代入とspread operatorとMapとSetが動く。
Promiseはオブジェクトはあるけど呼び出すとエラーが返る

ES6をふつうのJavaScript(ES5)に変換するツール


色々なブラウザ環境で実行させるとしたらES5に変換するのが現実的

babel
es6をes5に変換するトランスパイラ
元6to5
node 0.12なら問題なし、node 0.10でもiteraotr系以外動くJSが作れる
ES6でライブラリを書いてnpmに公開するとかにオススメ
新し目のブラウザでの実行もbabelで良さそう

traceur
es6をes5に変換
Google製

Browserify+babelify
es6からブラウザjsに変換、requireを辿って1ファイルに固めてくれるのでnode_modulesも読み込める

lint

ESLint+babel-eslint
es6をlintできる


ES6を実際使う

ES6は各ブラウザでもサポートしている機能としてない機能がバラバラなのでbabelでES5に変換してやるのが良いようだ。
ただ、for-ofのiteratorはbabelで変換すると変換後のコードにSymbol.iteratorが含まれているのでNode 0.10だと動かない。Node 0.12だと動く。

あとProxyはFirefoxでしか動かないっぽい。

色々試した感じ、例えばES6で書いてbabelで変換してからNodeのライブラリとしてnpmで配布する時はfor-ofだけ使わないように意識すれば事故は起こらないはず。
(MapやTypedArrayみたいな新しい組み込み型は使わないとして)


ES6と変換後のES5の動作をブラウザでも手軽に確かめたかったので、ちょっとしたツールを作った。
こんなかんじでgithubに置いてあるES6/ES5が実行できる

Herokuでも動いてて、サーバーはES6で書いてbabel-nodeで実行してる
https://shokai-es6-study.herokuapp.com/

0

flux.DispatcherのwaitForの実装

Promiseもコールバックも使ってないのにDispatcherのwaitForで順番の制御ができる。どういう事なのか気になったので調べた。


flux.Dispatcher

Reactはなんとなく使えるようになった気がしたので、fluxでやろう、StoreとかActionとかどう書くんだと調べてて、とりあえずflux npmというFacebookの中の人によるfluxアーキテクチャの説明とDispatcherが一つだけ入っているnpmを見ていた。

Dispatcherは使ってみた感じ、イベント名が無いEventEmitterみたいな感じで、登録順に実行される。

flux = require 'flux'
Dispatcher = new flux.Dispatcher

Dispatcher.register (action) ->
console.log "1 - #{JSON.stringify action}"

Dispatcher.register (action) ->
console.log "2 - #{JSON.stringify action}"

Dispatcher.register (action) ->
console.log "3 - #{JSON.stringify action}"

Dispatcher.dispatch
actionType: 'add-food'
food: 'beef'

Dispatcher.dispatch
foo: 'bar'
1 - {"actionType":"add-food","food":"beef"}
2 - {"actionType":"add-food","food":"beef"}
3 - {"actionType":"add-food","food":"beef"}
1 - {"foo":"bar"}
2 - {"foo":"bar"}
3 - {"foo":"bar"}


waitFor([id…])


なんかwaitForにregisterメソッドから返ってくるIDを渡すと、そのコールバック呼び出しの順序を制御できるらしい。便利そう

flux = require 'flux'
Dispatcher = new flux.Dispatcher

wait_ids = []

Dispatcher.register (action) ->
Dispatcher.waitFor wait_ids
console.log "1 - #{JSON.stringify action}"

wait_ids.push Dispatcher.register (action) ->
console.log "2 - #{JSON.stringify action}"

Dispatcher.register (action) ->
console.log "3 - #{JSON.stringify action}"

Dispatcher.dispatch
actionType: 'add-food'
food: 'beef'

2 - {"actionType":"add-food","food":"beef"}
1 - {"actionType":"add-food","food":"beef"}
3 - {"actionType":"add-food","food":"beef"}

何度もwaitFor

1つのコールバック内でwaitFor→なんか処理→またwaitFor→なんか処理 ができる。気持ち悪い。
flux = require 'flux'
Dispatcher = new flux.Dispatcher

wait_ids = []
wait_ids2 = []

Dispatcher.register (action) ->
Dispatcher.waitFor wait_ids
console.log "1 - #{JSON.stringify action}"
Dispatcher.waitFor wait_ids2
console.log "done!"

wait_ids.push Dispatcher.register (action) ->
console.log "2 - #{JSON.stringify action}"

wait_ids2.push Dispatcher.register (action) ->
console.log "3 - #{JSON.stringify action}"

console.log "wait_ids #{wait_ids}"
console.log "wait_ids2 #{wait_ids2}"

Dispatcher.dispatch
actionType: 'add-food'
food: 'beef'

wait_ids  ID_2
wait_ids2 ID_3
2 - {"actionType":"add-food","food":"beef"}
1 - {"actionType":"add-food","food":"beef"}
3 - {"actionType":"add-food","food":"beef"}
done!


waitForの実装

https://github.com/facebook/flux/blob/2.0.2/src/Dispatcher.js#L154-L177

waitForというより、指定idのコールバックを先に実行しているだけ。待っているように見えてるだけだった。

そもそもDispatcher.registerにはEventEmitterのようにイベント名は登録できないので、dispatchしたらregister済みの全てのコールバック関数は登録順に即実行される。(actionTypeなどのプロパティで自分宛てなのか判定してねという作法になってる)

dispatchすると、コールバックは実行直前に自分のidにisDispatchingフラグを立てる。
コールバック内でwaitFor([id…])を呼ぶと、id指定したコールバックをその場で実行しようとする。
対象がisDispatchingの場合は次のidを見る。
waitForで指定されたidのコールバックの中で、さらにwaitForが呼ばれたら再帰的にまた同じことをやる。

自分のidにはpendingフラグが立っていて、pendingなコールバック間でwaitForがループするとCircular dependency detected while…というエラーがthrowされる。

waitFor指定したコールバックの呼び出しが完了したら、そのまま処理が続くので順番が制御できる。