ここでPromiseオブジェクトを返す関数とasync関数に注目しよう。
function doLongTimeTask(n, msg) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('resolved: ' + msg), n);
});
}
async function doLongTimeTaskAsync(n, msg) {
var result = await doLongTimeTask(n, msg);
console.log(result);
}
doLongTimeTaskAsync(1000, 'async');
Promiseオブジェクトが実行する処理の中でresolve関数呼び出しが行われると、その引数の値が待機した関数(というか、Promiseオブジェクト)の戻り値となる。そのため、上のコードを実行すると「resolved: async」という文字列が変数resultには代入される。async関数の呼び出し側がresolve関数を与える必要はない(恐らく、組み込みのresolve/reject関数が提供され、これらの処理を行ってくれる。詳細はasync関数の仕様のドラフトなどを参照されたい)。
要するに、Promiseオブジェクトとthenメソッドを使った場合にresolve関数に記述していた処理は、awaitで待機したコード以降にそのまま記述できるようになっているということだ。
また、先ほども書いたように、reject関数を呼び出すと例外が発生する。そのため、catchメソッドをPromiseオブジェクトにチェーンさせるのではなく、try〜catch文で例外を補足するのがasync関数のやり方になる。以下に例を示す(あくまでもサンプルなので、setTimeout関数で指定された時間が経過したら、有無をいわさず、reject関数を呼び出している)。
function doLongTimeTask(n, msg) {
return new Promise((resolve, reject) => {
setTimeout(() => reject('rejected: ' + msg), n);
});
}
async function doLongTimeTaskAsync(n, msg) {
var result;
try {
result = await doLongTimeTask(n, msg);
console.log(result);
} catch(e) {
console.log(e);
}
}
doLongTimeTaskAsync(1000, 'async');
また、上記のコード例では「Promiseオブジェクトを返す関数呼び出しを待機している」が以下のような記述も可能だ。
async function doLongTimeTaskAsync(n, msg) {
var promise = doLongTimeTask(n, msg);
var result = await promise;
console.log(result);
}
名前から分かる通り、変数promiseはdoLongTimeTask関数の戻り値であるPromiseオブジェクトを参照し、変数resultにはそれを待機した結果(この場合は文字列)が代入される。
Promiseオブジェクトとthenメソッドを使った場合は、以下のようにthenメソッド/catchメソッドをチェーンさせて、非同期処理の実行に順序性を持たせることができる。
var promise = ...
promise.then(function(value) {
...
return value2;
}).then(function(value2) {
...
return value3;
}).then(function(value3) {
...
}).catch(function(reason) {
...
});
一方、async関数を使った場合には、非同期処理を行う箇所でawaitを使って待機するだけだ。例えば、以下のコードでは1秒ごとにコンソールにメッセージが表示される。
async function doLongTimeTaskAsync() {
var result;
result = await doLongTimeTask(1000, 'task1');
console.log(result);
result = await doLongTimeTask(1000, 'task2');
console.log(result);
result = await doLongTimeTask(1000, 'task3');
console.log(result);
}
doLongTimeTaskAsync();
Promiseオブジェクトをそのまま使用している上のコードも十分に分かりやすいものだが、比べてみると、async関数の方がさらに分かりやすいコードとなっている。
ここまで、setTimeout関数を使ってコンソールに出力するだけのコードを見てきた。そこで前回も使用したWebページの内容を取得する関数をasync関数から使用する例も見ておこう。といっても、基本は同じだ。
var http = require('http');
function getWebPage(url) {
return new Promise(
function(resolve, reject) {
var contents = '';
var req = http.request({ host: url, method: 'GET' }, function(res) {
res.on('data', function(chunk) {
contents += chunk;
});
res.on('end', function() {
resolve(contents);
});
});
req.on('error', function(reason) { reject("fail :" + reason.message); });
req.end();
});
}
async function getWebPageAsync(url) {
try {
var contents = await getWebPage(url);
console.log(contents);
} catch(e) {
console.log(e);
}
}
getWebPageAsync('www.microsoft.com');
非同期処理を行いPromiseオブジェクトを返すgetWebPage関数と、それを使用するgetWebPageAsync関数がある。後者ではtry〜catch文によりエラーが発生した場合には、その原因が分かるようになっている。興味のある方は、getWebPageAsync関数に存在しないURLを与えて実行してみよう。なお、このサンプルについてはBabelの「Try it out」ページでは動作しないので、手元の環境で試す必要がある。
それでは、本稿の最後にTypeScriptでのasync関数の仕様についても見ておこう。
TypeScript 1.7ではasync関数がサポートされているので、上記のコードもコンパイル可能だ。ただし、コンパイルターゲットをES2015(ES6)に設定する必要がある。コンパイル後のコードがES2015になるので、実行にはbabel-nodeコマンドを使うか、nodeコマンドに「--harmony」オプションを指定する必要がある。
例として、先ほど見た1秒ごとにコンソールに出力するコードをTypeScriptでコンパイルしてみよう。
>type asynctest.ts
function doLongTimeTask(n, msg) {
…… 省略 ……
}
async function doLongTimeTaskAsync() {
var result;
result = await doLongTimeTask(1000, 'task1');
console.log(result);
…… 省略 ……
}
doLongTimeTaskAsync();
>tsc --version
message TS6029: Version 1.7.5
>tsc --target es6 asynctest.ts
>node --harmony asynctest.js
resolved: task1
resolved: task2
resolved: task3
TypeScriptでのコンパイル時には「--target」オプション(または「-t」オプション)に「es6」を指定する。これにより、拡張子を「.js」とするファイルが生成される。後は「node --harmony」コマンドか「babel-node --presets es2015,stage-3」コマンドを使用して実行できる。
本稿ではES2016で導入されるといわれているasync関数の基本について見た。次回は、async関数と親和性の高い(というか、Babelでのasync関数のトランスパイルにおいてPromiseと組み合わせて使われている)ジェネレータ関数について見ていく予定だ。
Copyright© Digital Advantage Corp. All Rights Reserved.