C#開発者のための最新JavaScript事情(async関数編):特集:C#×JavaScript(1/2 ページ)
今回はPromiseオブジェクトをベースに、非同期処理をよりスッキリと記述できるようになるasync関数の基本について見ていこう。
powered by Insider.NET
前回はPromiseオブジェクトについて少し詳しく見た。今回はPromiseオブジェクトをベースとしたasync関数(async/awaitキーワード)による非同期処理の基本について見ていこう。なお、async関数は2016年1月21日時点で仕様策定が完了したわけではない。あくまでも現時点での話だと考えてほしい(仕様自体は決まっている段階なので大きく変わることはないだろう)。
async関数
C#開発者の方であれば、async/awaitキーワードによる非同期処理の記述は既にお手のものであろう。ECMAScript 2015(以下、ES2015)の次バージョンとなるECMAScript 2016(以下、ES2016)では、C#と似た構文でasync/awaitキーワードを使って非同期処理を記述できるようになる。C#ではTask(Task<T>)型のオブジェクトとasync/awaitキーワードを組み合わせていたが、ES2015ではPromiseとasync/awaitキーワードを組み合わせて使用する(両者のパターンが似ているのが意図的なものかどうかは不明だ)。
つまり、非同期処理を待機する箇所で「await」し、「await」を含んだ関数は「async」関数として宣言をするということだ。これにより、awaitした箇所で処理は中断し、制御はasync関数の呼び出し側へと返され、非同期処理が完了した時点でasync関数の残りのコードが実行されるようになる。これをECMAScriptの仕様策定を行っている「Ecma TC39」では「async関数」と呼んでいる。
以下に簡単な使用例を示す。C#のasync/awaitキーワードの使い方と同様だ。なお、doLongTimeTask関数はPromiseオブジェクトを返す関数だ(つまり、何らかの非同期処理を行う)。
async function doLongTimeTaskAsync(n, msg) {
var result = await doLongTimeTask(n, msg);
console.log(result);
}
awaitにより、doLongTimeTask関数が完了するのを待機しながら、制御は呼び出し元へ返される。そして、doLongTimeTask関数が成功して完了すると、以降の行(console.logメソッド呼び出し)が実行される。doLongTimeTask関数が失敗した場合には例外が発生する(そのため、本来は例外処理まで記述しておくのが正しいだろう。後述)。
では、Promiseオブジェクトとthenメソッドを使った場合のコードと、async関数を使った場合のコードとを比較してみよう。なお、本稿のコードは基本的にBabelの「Try it out」ページ、Windows 10/Mac(OS X)上のBabelコマンドラインツールで動作を確認している。
Promiseオブジェクトとthenメソッドを使った場合
doLongTimeTask関数のコードが以下のようなものだったとする。
function doLongTimeTask(n, msg) {
return new Promise(function(resolve, reject){
setTimeout(function() {
resolve('resolved: ' + msg);
}, n);
});
}
// アロー関数を使用した場合
function doLongTimeTask(n, msg) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('resolved: ' + msg), n);
});
}
この関数は、新規に作成したPromiseオブジェクトを返し、そのPromiseオブジェクトが行う非同期処理は「引数nに指定した時間(ミリ秒単位)が経過した後にresolve関数を呼び出す」ことだ。
前回と前々回のおさらいになるが、ES2015ではthenメソッドを使い、次のようなコードに非同期処理をスッキリと記述できる。
function doLongTimeTaskPromise(n, msg) {
var p = doLongTimeTask(n, msg);
p.then(function(value) { // value => console.log(value)
console.log(value);
}, function(reason) { // reason => console.log(reason)
console.log(reason)
});
}
console.log('begin');
doLongTimeTaskPromise(1000, 'promise');
console.log('end');
コメントアウトしているのは、アロー関数を使った場合のresolve関数/reject関数だ。
変数pが参照するPromiseオブジェクトは非同期に処理を行い、それが完了すると、thenメソッドに渡したresolve関数(あるいはreject関数)が呼び出される。処理が完了するのを待機している間、コードの実行は進む。よって、このコードの実行結果は次のようになる。
>babel-node --presets es2015 asynctest.es6
begin
end
resolved: promise
コンソールには「begin」と「end」の出力が先に行われ、最後にresolve関数の出力「resolved: promise」が行われる。
async関数を使った場合
async関数を使うと、同期処理を行うコードと似た形で非同期処理を記述できる。先ほども紹介したが、async関数を使った場合のコードを以下に示す。なお、トップレベルで直接「await doLongTimeTask(...)」のような記述はできないので注意しよう。
function doLongTimeTask(n, msg) {
…… 省略 ……
}
async function doLongTimeTaskAsync(n, msg) {
var result = await doLongTimeTask(n, msg);
console.log(result);
}
console.log('begin');
doLongTimeTaskAsync(1000, 'async');
console.log('end');
Promiseオブジェクトを使った場合でもコードはシンプルだが、then/catchメソッドを使ったPromiseオブジェクトに固有な記述の仕方になる。一方、async関数を使えば、関数宣言の先頭に「async」が、非同期処理を待機する箇所に「await」がある以外は、同期処理を書くのと同様に記述できる。
なお、Babelのコマンドラインツールで上記のコードをトランスパイルするには、「babel-preset-stage-3」プリセット(と「babel-preset-es2015」プリセット)を事前にインストールしておく必要がある(本稿執筆時点=2016年1月21日現在)。
筆者が試したところでは、前述の二つのプリセットをインストールすることで、自動的に必要なプラグインがインストールされたが、うまくいかないときにはBabelの「Plugins」ページ以下にある「Syntax async functions」ページの記述を参考にtransform-regeneratorプラグインやtransform-async-to-generatorプラグインなどをインストールして、必要な設定を「.babelrc」ファイルに記述しておこう。これらはasync関数を使用したコードをJavaScript 5.x環境でも実行できるように変換してくれるプラグインだ。
「babel-preset-stage-3」プリセットの「stage-3」は、ECMAScriptの仕様に関する議論のステージ(段階)を示している。「ステージ3」は「Candidate」(候補)を意味し、おおまかには「仕様は決定し、詳細を詰めるためにフィードバックを必要としている」段階となる(各ステージの詳細や仕様策定の手順などについては「The TC39 Process」ページなどを参照してほしい)。
本稿執筆時点でasync関数はステージ3になっていて、ステージ4になると、直近のECMAScript仕様に取り込まれる。そういうわけで、「babel-preset-stage-3」プリセットにはasync関数を使うためのプラグインが含まれているのである。
話を戻して、実行結果も一応示しておこう。ここではES2015のPromiseオブジェクトと、ES2016で導入予定のasync関数を使用しているので、「--presets」オプションに「es2015,stage-3」と二つのプリセットを指定している。
>babel-node --presets es2015,stage-3 asynctest.es6
begin
end
resolved: async
「--presets」オプションを毎回指定するのが面倒であれば、「.babelrc」ファイルに以下の記述をしておくとよい。
{
"presets": ["es2015", "stage-3"]
}
Copyright© Digital Advantage Corp. All Rights Reserved.