検索
特集

C#開発者のための最新JavaScript事情(async関数編)特集:C#×JavaScript(2/2 ページ)

今回はPromiseオブジェクトをベースに、非同期処理をよりスッキリと記述できるようになるasync関数の基本について見ていこう。

Share
Tweet
LINE
Hatena
前のページへ |       

Promiseオブジェクトとasync関数

 ここで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オブジェクトで行う非同期処理と、それを待機する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');

reject関数呼び出しは例外を発生させる

 また、上記のコード例では「Promiseオブジェクトを返す関数呼び出しを待機している」が以下のような記述も可能だ。

async function doLongTimeTaskAsync(n, msg) {
  var promise = doLongTimeTask(n, msg);
  var result = await promise;
  console.log(result);
}

Promiseオブジェクトをawaitした結果を変数に代入Promiseオブジェクトをawaitした結果を変数に代入

 名前から分かる通り、変数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();

awaitを使って、非同期処理を順序よく実行する

 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');

Webページを取得するasync関数

 非同期処理を行いPromiseオブジェクトを返すgetWebPage関数と、それを使用するgetWebPageAsync関数がある。後者ではtry〜catch文によりエラーが発生した場合には、その原因が分かるようになっている。興味のある方は、getWebPageAsync関数に存在しないURLを与えて実行してみよう。なお、このサンプルについてはBabelの「Try it out」ページでは動作しないので、手元の環境で試す必要がある。

 それでは、本稿の最後にTypeScriptでのasync関数の仕様についても見ておこう。

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でasync関数を含むコードをコンパイル/実行

 TypeScriptでのコンパイル時には「--target」オプション(または「-t」オプション)に「es6」を指定する。これにより、拡張子を「.js」とするファイルが生成される。後は「node --harmony」コマンドか「babel-node --presets es2015,stage-3」コマンドを使用して実行できる。


 本稿ではES2016で導入されるといわれているasync関数の基本について見た。次回は、async関数と親和性の高い(というか、Babelでのasync関数のトランスパイルにおいてPromiseと組み合わせて使われている)ジェネレータ関数について見ていく予定だ。

「特集:C#×JavaScript」のインデックス

特集:C#×JavaScript

Copyright© Digital Advantage Corp. All Rights Reserved.

前のページへ |       
ページトップに戻る