2016年4月13日水曜日

ECMAScript2015 (ES6) のまとめ / Promise, Set, Map

mainpicture

さて、しばらく間があいてしまいました。ES6の最終話です。

前回までの話で大きなところは大体抑えられたかと思います。今回は、いままで外部のライブラリ等に頼っていた部分がES6にて標準実装になったモノがあるのでその辺りをまとめていきます。

より詳細を確認したい方はECMAScript2015の公式を確認すると良いかと。


ECMAScript2015のまとめ

ES5と比較した時のES6の特徴としては以下のものが挙げられます。(言語仕様を読むと他にもいろいろと定義はされています)

  • let
  • template string
  • arrow function ( => )
  • default parameter ( function(x, y=10) { )
  • spread operator ( ...[1,2,3] )
  • rest parameter ( function(x, y, ...z) { )
  • class
  • import, export
  • Promise
  • Set, Map(今日はここまで)

今回はPromise, Set, Mapを説明していきます!


Promise

webサービスを開発したことがある方なら触ったことがあるかもしれません。非同期処理を書いていく際にcallbackでネストが深くなってしまうような箇所をthen関数やcatch関数を利用することにより、浅く見通しよく書けるようになります。

angularJSではqというライブラリが利用されていたと記憶しています。

まずはES5にてネストがいくつか連なっている例を挙げると、

// ES5 ネスト
var func1 = function(cb) {
  console.log('in func1');
  cb();
}

var func2 = function(cb) {
  console.log('in func2');
  cb();
}

var func3 = function(cb) {
  console.log('in func3');
  cb();
}

func1(function() {
  func2(function() {
    func3(function() {
      console.log('last call');
    });
  });
});

このような形になります。私は別にこれで書いても全く悪くないとは思っていますし、azureのnodejsのsdkの内部にもこういった内容が結構あります。ただ、例外処理などが入ってくると徐々に書きにくくなっていったりするのも事実です。

callback形式で例外処理をするさいには、callbackの第一引数にErrorを詰めて返すのが割とよく見られるやり方です。

func1(function(err) {
});

このような形になります。他のやり方として、第二引数に例外時の関数を渡したりという設計も可能ですが、個人的にはかなり読みにくくなるのであまり好きではなありません。

func1(function() {
// 正常処理
}, function(err) {
// 例外処理
});

さてこれをES6のpromiseを使って書き換えてみます。

// ES6 promise
var func1 = function(cb) {
  return new Promise(function(resolve) {
    console.log('in func1');
    resolve();
  });
}

var func2 = function() {
  console.log('in func2');
}

var func3 = function() {
  console.log('in func3');
}

func1().
then(func2).
then(func3).
then(function() {
  console.log('last call');
});

promiseを使うことにより、func2とfunc3がcallbackを呼び出すという役割は不要になりました。シンプルにその関数で必要な処理を記載するだけです。

値を次のfunctionへ渡していきたい場合にはreturnをすれば次の関数で受け取ることができます。

var func2 = function() {
  console.log('in func2');
  return 'from func2'
}
var func3 = function(message) {
  console.log('in func3');
  console.log(message);
}

また、エラー処理も読みやすく書くことができます。

// ES6 promise
var func1 = function(cb) {
  return new Promise(function(resolve) {
    console.log('in func1');
    resolve();
  });
}

var func2 = function() {
  console.log('in func2');
  throw new Error('oh no');
}

var func3 = function() {
  console.log('in func3');
}

func1(). // in func1
then(func2). // in func2
then(func3). // ここは実行されない
then(function() { // ここも実行されない
  console.log('last call');
}).catch(function(e) {
  console.log(e); // [Error: oh no]
});

catch関数を呼ぶことにより、読みやすい例外処理を行うことが可能です。

実はnew Promiseの引数に渡している関数の第二引数にrejectを渡すことも可能です。rejectを利用するとfunc1から明示的にcatch関数まで飛ばすことができます。

var func1 = function(cb) {
  return new Promise(function(resolve, reject) {
    console.log('in func1');
    if (true) {
      reject();
    }
    resolve();
  });
}

ただし注意すべき点があります。それは、ここでrejectした場合、thenの第二引数で処理を受けることも可能であるということです。

func1().
then(func2, func3).
then(func3).
then(function() {
  console.log('last call');
}).catch(function(e) {
  console.log(e);
});

このような形になっている場合、func1の後の処理は2行目のfunc3に渡ることになります。

Promiseに関してはこれくらいです。慣れるまで少し時間がかかりますが、比較的読みやすくなるので個人的には好きです。構文がrubyのblockに少し似ていて(chainはできないけど)そこまで読みにくさは感じませんでした。


Set, Map

二つまとめていきましょう。SetとMapについてです。

まずはSet。

Setは重複しない値を保持するためのデータ構造になります。

var s = new Set([1, 2, 3, 'aaa', 'bbb']);
console.log(s);
console.log(s.entries());
s.add(1)
s.add('ccc')
console.log(s);
var s = new Set([1,1,1,1]); // Set[1]
console.log(s);

これで大体概要はつかめるのではないでしょうか。重複した値を保持しないようにできているため、大量データをfilterしながらループ処理するときなどSetとaddを使えば重複する可能性などを考えずシンプルにコーディングすることができます。(arrayのfilter使えよという話もありますがw)

ちなみに値が同一であるというのはECMAScript6 7.2.10SameValueZeroで判定されているようです。興味のある方は目を通してみると良いかと思います。

次はMap。

オブジェクトと少し似ているのですが、keyに関数を指定したり、getやsetやkeyの取得などの関数が用意されています。

var data = new Map([
    ['a', 'aaa'],
    ['b', 'bbb'],
]);

console.log(data);
console.log(data.get('a'));
console.log(data.size);

for (var k of data.keys()) { // Iterator
  console.log(k);
}

と例を出してみたものの、個人的にはオブジェクトで十分であるような気もしています。そこまでこの構造がないと困ったというような局面になったことがないので、いまのところ有用性があまりわかっていないです。keyを取得するのが簡単になったのはかなり嬉しいですが。(これまではObject.keys(data)とかやってた)

最後の最後に肩透かしで申し訳ない。


さて、これにてECMAScript6のメインとなりそうなトピックの説明が完了しました。いろいろな言語がありますが、Chrome or node(V8), Firefox(Gecko?最近変わった??), Safari(webkit)などメジャーな実行環境・実装がいくつもある言語もそうないのではないかと思っています。しかも開発する際にはある程度気を配る必要があるという…。そんな中で標準というものが出来上がってきつつあり、プログラマとしては非常にありがたいです。

jsに関してはこれからも出来ることが増えていくことに期待しています。言語的には非常に柔軟で、スモールプロダクトであればかなり早く、かつ、クライアントもサーバーも同じ言語で記述できるという良い特性を備えつつあると思い活用しています。

次は今作っているwebサービスの話なんかができれば良いかな。

では、良いインプットと良いプログラミングを。