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にて,**「既知の問題」*によりEdgeのasync/awaitが使えなくなりました。Windows 10 FASTで環境構築をしてもAsync Functionが使えない状態となってしまいました。次回のリリースに期待しましょう。
【重要】2015/10/14 11:32 追記
※Windows 10 Insider Build 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 正常系の挙動
Edge Async Functions 異常系の挙動
ES7/ES2016に正式採用されるのか
Async Functionは2015/9にStage 3(Candidate)となりました。
Async Functionは 2015/11末にStage 4 (Finished) 正式採用 となる予定です。
Edge vs Babel Async Functions
Async Functions | Edge ( |
Babel |
---|---|---|
適合状況 |
|
Bad |
デバッグ | Good | Bad |
ファイルサイズ | Good | Bad |
実行速度 | Good |
|
適合状況
EdgeのBad Good理由
Edge (10547)では,await中に発生させたthrow(例外)をtry-catchでcatchできなかった点がマイナスです(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,Bable)。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
ファイル | 説明 |
---|---|
package.json | ビルドに必要なライブラリ |
gulpfile.js | ビルドファイル |
src/ | |
index.html | 画面 |
app.js | ES7 Async Functions |
bigdata.json | テストデータ |
起動
# | コマンド | 説明 |
---|---|---|
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
{
"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
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
<!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
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
{
"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
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コミュニティに障害レポートしましょう。
参考
[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/