まだPromiseで消耗してるの？ ES7 Edge Async vs Babel Async Functions （ネイティブ vs トランスパイラ） 

# ES7/ES2016 Async Functions

　ECMAScript7 (ES7) (a.k.a. ES2016) Async FunctionがWebブラウザでそのまま（ネイティブ）動作する現在（2015/10/12）唯一のブラウザ「Microsoft Edge」（評価版 Build ~~10547~~ 10565）と，ES5に変換（トランスパイル）して動作させる「Babel」のAsync Functionの挙動やパフォーマンスを簡単に比較してみました。

　***【重要】2015/10/13 11:23　追記***
~~*※10/12にリリースされた[Windows 10 Insider Build 10565](https://dev.modern.ie/platform/changelog/desktop/10565/)にて，**「既知の問題」**によりEdgeのasync/awaitが使えなくなりました。Windows 10 FASTで環境構築をしてもAsync Functionが使えない状態となってしまいました。次回のリリースに期待しましょう。*~~

　***【重要】2015/10/14 11:32　追記***
　*※[Windows 10 Insider Build 10565](https://dev.modern.ie/platform/changelog/desktop/10565/)でもAsync Functionが動作しました。さらにawait中のthrowがcatchできないバグもFixされ，素晴らしいです。EdgeのURLにabout:flagsを入力して「試験的なJavaScript機能を有効にする」と使用できます。*

## Async Functionsとは

　Async FunctionはJavascriptにおける非同期処理の可読性とメンテナンス性を向上させるための構文です。非同期処理で有名になったのはES6/ES2015でも採用された"Promise"ですが，Promiseは結果の取り出しが依然としてcallbackスタイルです。~~ES5以前のcallback地獄の根本的な解決には至っていません。~~ PromiseによってES5以前のcallback地獄は回避することが可能となりましたが，結果を取り出すcallbackやメソッドチェーンの可読性は決してエレガントではありません。

## Async Functionsのメリット

 * 非同期処理の処理結果を受け取って，後続処理を同期的に記述可能（コールバック不要）
 * 非同期処理の例外を受け取って，例外発生時の処理を同期的に記述可能

## Edge Async Functions 正常系の挙動

![async_edge.gif](https://qiita-image-store.s3.amazonaws.com/0/67778/2d4f7366-ecc8-8519-7e6f-a0cb08b928d6.gif)

## Edge Async Functions 異常系の挙動

![async_edge_abnormal.gif](https://qiita-image-store.s3.amazonaws.com/0/67778/53295743-2a74-e32b-5b2d-3ab10d79db36.gif)


## ES7/ES2016に正式採用されるのか

　Async Functionは2015/9にStage 3（Candidate）となりました。
　Async Functionは **2015/11末にStage 4 (Finished) 正式採用** となる予定です。

# Edge vs Babel Async Functions

| Async Functions | Edge (~~10547~~ 10565) | Babel |
|-----|------|--------|
| 適合状況 | ~~Bad~~ Good | Bad |
| デバッグ | Good | Bad | 
| ファイルサイズ | Good | Bad |
| 実行速度 | Good | ~~Good~~ Bad |

## 適合状況

　**Edgeの~~Bad~~ Good理由**
　　~~Edge (10547)では，await中に発生させたthrow（例外）をtry-catchでcatchできなかった点がマイナスです（[Edge Async Functions 異常系の挙動](#edge-async-functions-異常系の挙動) 参照）。~~

　　~~Async Functionのメリットは，非同期処理で陥るcallback地獄からの解放と，正常時の後続処理や例外処理がコールバックではなく同期的に記述できる点が重要な要求仕様ですので，今後の改善に期待しています。~~
　　
　　Edge (10565)より，await中に発生させたthrowがcatchできました。
　　Edge (10565)はawait中にUIスレッドもブロックされません。

　**BabelのBad理由**
　　Babelでもawait中に発生させたthrowをcatchできました。
　　Edge (10565) ではUIスレッドがノンブロッキングで素晴らしいのですが，FirefoxやChromeではUIスレッドがブロックされてしまい何も操作ができない点がマイナスです
　
## デバッグ

　**EdgeのGood理由**
　　Edgeではasync/awaitがネイティブで動作するため，記述したとおりにJSがデバッグできます。async/awaitの挙動が確認し易く，メンテナンスもし易いです。素晴らしいです。

　**BabelのBad理由**
　　Babelではasync/awaitがトランスパイルされて動作するため，記述したJSのままではデバッグができません。console.logデバッグに頼るなど，動作の確認がし難いです。（エラー等はもちろんsource mapでも確認できます）

## ファイルサイズ

　**EdgeのGood理由**
　　ファイルサイズ： 2,696 bytes (4 KB on disk)
　　PageSpeedの観点でEdgeをGoodとしました。

　**BabelのBad理由**
　　ファイルサイズ： 163,006 bytes (164 KB on disk)
　　PageSpeedの観点でBabelをBadとしました。（Edgeの41倍）

## 実行速度

　**EdgeのGood理由**
　　　24,696ms
　　　24,948ms
　　　24,942ms
　
　**Babelの~~Good~~ Bad理由**
　　　64,373ms
　　　64,686ms
　　　64,698ms
　
　Edge (10565) のネイティブはEdgeで動作させたBabelのコードより約2.6倍高速でした。
　
　（測定環境： XHRを1,000回実施（[Edge](https://gist.github.com/k--kato/7a83d10fb51ac45f1202)，[Bable](https://gist.github.com/k--kato/665459eef768a1957cd0)）。Windows 10 Pro Insider Preview Build 10565, CPU Core i7-2600, RAM 16GB）

## 結論

 * Async Functionの動作を確認するにはEdgeが最適
 * Edge (10565) ではAsync Functionのネイティブとトランスパイラとで，**ネイティブが約2.6倍高速**
 * Edge (10565) ではAsync Functionのネイティブとトランスパイラの両方がUIスレッド・ノンブロッキング

# 環境構築

　Async Functionをネイティブで動作させるには，Windows 10 Insider Preview Build 10565 が必要です。Build 10565の入手方法は，Insiderプログラムに参加して，以下の設定とOSの再インストールが必要です。（所要時間は約2時間）

| # | 手順 | 操作 |
|-----|------|------|
1 | Insiderプログラムに参加                   | スタートボタン ＞　設定　＞　更新とセキュリティ　＞　Windows Update　＞　詳細オプション　＞　Insider ビルドの入手　＞　開始する　＞　Fast
2 | Insider Hubのインストール                 | スタートボタン ＞　設定　＞　アプリと機能　＞　オプション機能を管理する　＞　機能の追加　＞　Insider Hub　＞　インストール
3 | Insider Preview Build 10547のインストール | スタートボタン ＞　すべてのアプリ　＞　Insider Hub　＞　Insider のプロファイル　＞　デバイスとアクティビティ　

# 実装

　GitHubに掲載しました。ビルドに必要なライブラリやビルドを自動化していますので，nodeやgulp等のパッケージマネージャやビルドシステムの使い方を学びたい方もぜひ気軽に試してみてください。（動作しない，書き方が微妙などはpull requestを恐れ入りますがよろしくお願いします）
　
　‡GitHub, EdgeAsync, [https://github.com/k--kato/EdgeAsync](https://github.com/k--kato/EdgeAsync)
　

| ファイル | 説明 |
|-----|------|
[package.json](#packagejson) | ビルドに必要なライブラリ
[gulpfile.js](#gulpfilejs)   | ビルドファイル
src/                          |
　[index.html](#indexhtml)   | 画面
　[app.js](#appjs)           | ES7 Async Functions
　[bigdata.json](#bigdatajson) | テストデータ

# 起動

| # | コマンド | 説明 |
|-----|------|------|
1 | npm install | コマンドラインで実行。ライブラリのインストール
2 | npm run gulp default | コマンドラインで実行。ビルド
3 | Edgeのアドレスバーにabout:flags または about:configを入力　＞　JavaScript　＞　試験的な JavaScript を有効にする | ES7 Asyncを有効にする
4 | dist/index.html | ビルドされたファイルをEdgeで開く

# ソースコード

## package.json
```javascript
{
  "main": "gulpfile.js",
  "dependencies": {
    "babel": "^5.8.23",
    "babelify": "^6.3.0",
    "gulp-concat": "^2.6.0",
    "gulp": "^3.9.0",
    "gulp-html-minifier": "^0.1.6",
    "browserify": "^11.2.0",
    "gulp-rename": "^1.2.2",
    "gulp-sourcemaps": "^1.6.0",
    "gulp-minify-css": "^1.2.1",
    "vinyl-buffer": "^1.0.0",
    "vinyl-source-stream": "^1.1.0",
    "gulp-eslint": "^1.0.0",
    "bootstrap": "^3.3.5",
    "gulp-util": "^3.0.6",
    "gulp-uglify": "^1.4.1"
  },
  "devDependencies": {},
  "scripts": {
    "gulp": "gulp"
  },
  "repository": {
  },
  "license": "MIT"
}
```

## gulpfile.js

```javascript
var gulp = require('gulp');
var browserify = require('browserify');
var babelify= require('babelify');
var util = require('gulp-util');
var buffer = require('vinyl-buffer');
var source = require('vinyl-source-stream');
var concat = require('gulp-concat');
var rename = require('gulp-rename');
var htmlmin = require('gulp-html-minifier');
var sourcemaps = require('gulp-sourcemaps');

var js_edge_dir = [
    'src/app.js'
];

var js_babel_src =
    'src/app.js'
;

var html_dir = [
    'src/**/*.html'
];

var data_dir = [
    'src/bigdata.json'
];

// Edge Concatenate
gulp.task('js-edge', function () {
    return gulp.src(js_edge_dir)
        .pipe(concat('all.edge.js'))
        .pipe(gulp.dest('dist/js'))
        ;
});

// Babel & Concatenate
gulp.task('js-babel', function () {
    return browserify(js_babel_src, { debug: true })
        .add(require.resolve('babel/polyfill'))
        .transform(babelify)
        .bundle()
        .on('error', util.log.bind(util, 'Browserify Error'))
        .pipe(source('all.babel.js'))
        .pipe(buffer())
        .pipe(sourcemaps.init({loadMaps: true}))
        .pipe(sourcemaps.write('./'))
        .pipe(gulp.dest('./dist/js'));
});


// Minify HTML
gulp.task('html', function() {
    gulp.src(html_dir)
        .pipe(htmlmin({collapseWhitespace: true}))
        .pipe(gulp.dest('dist'))
});

// Copy Data
gulp.task('data', function() {
    gulp.src(data_dir)
        .pipe(gulp.dest('dist/data'))
});

// Watch
gulp.task('watch', function(){
    gulp.watch(js_edge_dir, ['js-edge']);
    gulp.watch(js_babel_src, ['js-babel']);
    gulp.watch(html_dir, ['html']);
});

// Default Task
gulp.task('default', [
    'js-edge'
  , 'js-babel'
  , 'html'
  , 'data'
  , 'watch'
]);
```

## index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
<button id="btn">Get JSON Async</button>
<div id="json"></div>
<script src="js/all.edge.js"></script>
<!--<script src="js/all.babel.js"></script>-->
</body>
</html>
```

## app.js

```javascript
async function xhrAsync(/* String */ url) {
    let request = new XMLHttpRequest();
    await request.open('GET', url, false);
    await request.send();
    if (request.status == 200) {
        return request.responseText;
    } else {
        throw Error(request.statusText);
    }
}

window.addEventListener("load", function() {
    const btn = document.getElementById('btn');
    btn.onclick = async () => {
        const json = document.getElementById('json');
        try {
            const /* String */ response = await xhrAsync('data/bigdata.json');
            json.innerText = response;
        } catch (/* String */ e) {
            json.innerText = e;
        }
    };
});
```

## bigdata.json

```javascript
{
  "count": 567,
  "next": "http://marsweather.ingenology.com/v1/archive/?terrestrial_date_end=2015-09-30&terrestrial_date_start=2012-01-01&page=2",
  "previous": null,
  "results": [
    {
      "terrestrial_date": "2015-09-30",
      "sol": 1120,
      "ls": 48.0,
      "min_temp": -80.0,
      "min_temp_fahrenheit": -112.0,
      "max_temp": -24.0,
      "max_temp_fahrenheit": -11.2,
      "pressure": 900.0,
      "pressure_string": "Higher",
      "abs_humidity": null,
      "wind_speed": null,
      "wind_direction": "--",
      "atmo_opacity": "Sunny",
      "season": "Month 2",
      "sunrise": "2015-09-30T11:12:00Z",
      "sunset": "2015-09-30T22:59:00Z"
    }
  ]
}
```


# 付録

## asyncの読み方について

　アシンク vs エイシンクですが，Async Functionを解説するMicrosoftのエンジニア間でもasyncの読み方が割れています。僕はアシンク派です。

　[Async Functions in JavaScript with Etienne Baudoux](https://channel9.msdn.com/Blogs/Seth-Juarez/Async-Functions-in-JavaScript-with-Etienne-Baudoux?ocid=player)


## Virtual BoxでHost OSにアクセスするには？

　Virtual BoxでGuest OS (Windows 10)からHost OS(OSX)にアクセスするにはNATだと10.0.2.2です。
　Host OSのOSXからWebStormで起動したindex.htmlに，Guest OSのWindowsからアクセスする例：
　　http://10.0.2.2:63342/EdgeAsync/dist/index.html

## 他のブラウザは正常にネットワークに接続できるが，Edgeだけ「このページに到達できません」が表示される

　TCP/IPv4のDNSを8.8.8.8（Google Public DNS）に設定すると一時的に解決する場合があります。

## Edgeの開発者ツールが落ちる

　開発者ツールを表示してもデバッグ画面が真っ白だったり，突然開発者ツールが真っ白になることがあり，何度か再度読み込みを繰り返してデバッグできる状態に復旧していました。
　Async Functionをプロファイラで計測すると，（僕の環境では）計測後に結果を閲覧しようとすると必ずEdgeが落ちてしまいます。
　try-catchの直後にブレークポイントが設定できないこともありました。
　Preview版ですので安定版を待つor[コミュニティに障害レポート](http://answers.microsoft.com/ja-jp/insider/forum)しましょう。

## 参考

　[1] Microsoft Edge Dev Blog, 
"Asynchronous code gets easier with ES2016 Async Function support in Chakra and Microsoft Edge", https://blogs.windows.com/msedgedev/2015/09/30/asynchronous-code-gets-easier-with-es2016-async-function-support-in-chakra-and-microsoft-edge/
　[2] TC39, "Async Functions Stage 3 Draft / October 9, 2015", https://tc39.github.io/ecmascript-asyncawait/
　[3] GitHub azu, 「明日には使えなくなるES7トーク」, http://azu.github.io/slide/es6talks/
　[4] Windows Insider Program, 「Windows Insider Preview の使い方」, http://windows.microsoft.com/ja-jp/windows/preview-how-to?ocid=tp2_nav_howto
　[5] Microsoft Edge Dev, "Windows Insider Preview Build 10565", https://dev.modern.ie/platform/changelog/desktop/10565/