V8 6.3で追加されたECMAScriptの機能 (Dynamic import、Asynchronous Iteration、 Promise.prototype.finally)

この記事ではV8に追加されたECMAScriptの機能を簡単に説明します。
[English](https://dev.to/shisama/v8-63-shipped-new-ecmascript-features-e3j)

V8 6.3が2017年10月25日にリリースされ、Chrome 63で使われています。(参考: [V8 JavaScript Engine](https://v8project.blogspot.jp/2017/10/v8-release-63.html)) 
このリリースでは[ECMAScriptのstage-3](https://github.com/tc39/proposals#stage-3)から3機能が追加されました。 
そのうちNode.jsにも入るでしょう。 

harmonyオプションやライブラリを使用すれば現時点(2017/12/02)でも使用可能です。
Node8、Node9またはChromeで実行できる[サンプル](https://github.com/shisama/v8-6.3-es-features)を用意しています。

# 概要
- [Dynamic import](#dynamic-import)
- [Async Iteration](#async-iteration)
- [Promise.prototype.finally](#promiseprototypefinally)

# Dynamic import
[仕様](https://tc39.github.io/proposal-dynamic-import/)

動的にモジュールをimportする機能です。
例えば以下のようにmoduleAまたはmoduleBの関数どちらかしか実行しない場合

```javascript
if (member.isLoggedIn()) {
  // ログインしている場合はmoduleAの関数を使いたい
} else {
  // ログインしていない場合はmoduleBの関数を使いたい
}
```

普通に静的なimportを使うと

```javascript
import moduleA from './moduleA';
import moduleB from './moduleB';

if (member.isLoggedIn()) {
  // ログインしている場合はmoduleAの関数を使いたい
  moduleA.func();
} else {
  // ログインしていない場合はmoduleBの関数を使いたい
  moduleB.func();
}
```

となり、両方をimportしなければいけません。

しかし、動的にimportできるようになると

```javascript
if (member.isLoggedIn()) {
  // ログインしている場合はmoduleAの関数を使いたい
  import('./moduleA.js') // ここでmoduleAが読み込まれる
    .then(module => {
      module.func();
    });
} else {
  // ログインしていない場合はmoduleBの関数を使いたい
  import('./moduleB.js') // ここでmoduleBが読み込まれる
    .then(module => {
      module.func();
    });
}
```

と実行するモジュールだけを動的に読み込むことができます。

他にも、ループの中でimportしたりPromiseのthenの中で読み込んだりすることができます。

```javascript
for (let i = 0; i < MAX; i++) {
  import(`module${i}.js`)
    .then(module => {
      module.func();
    });
}
```

```javascript
promiseFunc()
  .then(res => {
    return import('./module.js');
  })
  .then(module => {
    module.func(data);
  });
```

また、これまで見てきたとおり`import()`関数はPromiseオブジェクトを返すので、
async/awaitで書くこともできます。


```javascript
(async() => {
  if (member.isLoggedIn()) {
    // ログインしている場合はmoduleAの関数を使いたい
    const module = await import('./moduleA.js') // ここでmoduleAが読み込まれる
    module.func();
  } else {
    // ログインしていない場合はmoduleBの関数を使いたい
    const module = await import('./moduleB.js') // ここでmoduleBが読み込まれる
    module.func();
  }
})()
```

[Demo](https://shisama.github.io/v8-6.3-es-features/demo/dynamic_import.html)を用意しました。
Chrome 63以降でアクセスし、コンソールやネットワークを見ていただけると動的に読み込まれていることが確認できると思います。
コードは以下のとおりです。

```javascript
(async() => {
    let count = 0;
    // 1秒ごとにhello1、hello2、hello3をランダムで読み込む
    let id = setInterval(async() => {
      const i = Math.floor(Math.random() * 3) + 1;
      const module = await import(`./import_modules/hello${i}.js`);
      module.hello();
      count++;
      if (count === 10) {
          clearInterval(id);
      }
     }, 1000);
})();
```

Nodeでは[node-es-module-loader](https://www.npmjs.com/package/node-es-module-loader)にて実行するとつかえます。
(※`--harmony_dynamic_import`のオプションがありますが正しく動作しませんでした｡ﾟ(ﾟ´Д｀ﾟ)ﾟ｡）
[サンプル](https://github.com/shisama/v8-6.3-es-features)からご確認ください。

# Async Iteration
[仕様](https://tc39.github.io/proposal-async-iteration/)

a.k.a Async Iterators / Generators
非同期処理を反復実行することが可能になります。
iteratorやgeneratorに関しては[こちら](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Iterators_and_Generators)を参考にしてください。

例えば、非同期な関数やAPIを複数回実行したい場合以下のように`Promise.all`を使って並列実行することができます。

```javascript
(async() => {
  const dataList = await Promise.all([
    fetch('https://qiita.com/api/v2/tags/Node.js'),
    fetch('https://qiita.com/api/v2/tags/JavaScript'),
    fetch('https://qiita.com/api/v2/tags/npm'),
  ]);
  for (const data of dataList) {
    console.log(data);
  }
})();
```

しかし、実行したい関数やAPIが増えると対応しきれなくなります。
反復処理で対応できる場合、async iteratorsを使うと以下のように書けます。

```javascript
(async() => {
  const promises = [];
  for (const tag of ["Node.js", "JavaScript", "npm"]) {
    promises.push(fetch(`https://qiita.com/api/v2/tags/${tag}`));
  }
  
  for await (const data of promises) {
    console.log(data)
  }
})();
```

さらにasync generatorsを使うと無限ループに対する独自のイテレーションを行えます。
[Demo](https://shisama.github.io/v8-6.3-es-features/demo/async_iteration.html)を用意しました。
Chrome 63以降でアクセスし、コンソールやネットワークを見ていただけるとAPIが実行されていることが確認できると思います。

コードは以下のとおりです。HTTPクライアントには[axios](https://github.com/axios/axios)を使ってます。

```javascript
/*
 * 乱数を取得
 */
async function* gen() {
  while (true) {
    const res = await axios.get('https://www.random.org/decimal-fractions/?num=1&dec=10&col=1&format=plain&rnd=new');
    const num = res.data;
    yield Number(num);
  }
}

(async() => {
  const BREAK = 0.8;
  for await (const num of gen()) {
    console.log(num);
    if (num > BREAK) {
      console.log("over", BREAK);
      break;
    }
  }
})();
```

Nodeでは`--harmony_async_iteration`オプションを付けて実行すると使えます。
[サンプル](https://github.com/shisama/v8-6.3-es-features)からご確認ください。

# Promise.prototype.finally
[仕様](https://tc39.github.io/proposal-promise-finally/)

try-catch-finallyのfinallyです。
finallyに関しては説明不要かと思います。
エラーになってもならなくても必ず実行してほしい処理は`then`と`catch`の両方に書かなければいけませんでした。

```javascript
promiseFunc()
  .then(() => {
    someFunction();
    closeFunction(); // 成功した場合用
  })
  .catch(err => {
    console.log(err);
    closeFunction(); // エラーになった場合用
  });
```

しかし、普通にtry-catch-finallyで書けるようになります。

```javascript
promiseFunc()
  .then(() => {
    someFunction();
  })
  .catch(err => {
    console.log(err);
  })
  .finally(() => {
    closeFunction(); // 必ず実行される
  });
```

ちなみにasync/awaitを使えば現在でもtry-catch-finallyは使えます。

```javascript
(async() => {
  try {
    await promiseFunc();
    await someFunction();
  } catch (err) {
    console.log(err);
  } finally {
    closeFunction();
  }
})();
```

こちらも[Demo](https://shisama.github.io/v8-6.3-es-features/demo/promise_finally.html)を用意しました。
以下コードです。

```javascript
Promise.resolve("resolve")
  .then(val => {console.log(val);return "then"})
  .then(val => {console.log(val);throw new Error("catch")})
  .catch(err => {console.log(err.message)})
  .finally(() => {console.log("finally")});
```

Chrome 63以降でアクセスし、コンソールを見ていただけると`resolve()`、`then()`、`catch()`、`finally()`の処理が実行されていることが確認できると思います。

Nodeでは`--harmony_promise_finally`オプションを付けて実行すると使えます。
[サンプル](https://github.com/shisama/v8-6.3-es-features)からご確認ください。

# まとめ
- ECMAScriptの新機能がV8に入った
- Chromeでは63以降使える
- V8には実装されたので~~そのうちNodeにも入る~~Node v10に入ります

# 参考
- [V8 JavaScript Engine: V8 Release 6.3](https://v8project.blogspot.jp/2017/10/v8-release-63.html)
- [tc39/proposal-dynamic-import](https://tc39.github.io/proposal-dynamic-import/)
- [tc39/proposal-async-iteration](https://tc39.github.io/proposal-async-iteration/)
- [tc39/proposal-promise-finally](https://tc39.github.io/proposal-promise-finally/)
- [Async iterators and generators - JakeArchibald.com](https://jakearchibald.com/2017/async-iterators-and-generators/)
- [Promise.prototype.finally | blog.jxck.io](https://blog.jxck.io/entries/2017-08-10/promise-finally-proposal.html)

# 謝辞
この記事を書くにあたって[@shimataro999](https://twitter.com/shimataro999)にレビューしていただきました。
この場を借りてお礼申し上げます。ありがとうございました。

最後までお読み頂きありがとうございました。
質問や不備等はコメントにてお願い致します。