67
54

More than 5 years have passed since last update.

async awaitでキレイなコードが書けないと辛い気持ちになってたところ、zone.jsを使ったらいろいろと解決できそうなことが分かった

Last updated at Posted at 2016-03-06

zone.jsとは

zone.jsとは、Angular2で使われているユーティリティーです。zone.jsを使うことで、非同期のミドルウェアを簡単に作ることができます。

非同期処理の問題点

Javaの同期処理では、ThreadLocalを利用することで、スレッドに紐づくコンテキストデータを引き継ぐことができました。
しかし、非同期処理ではThreadは再利用されるため、Threadにコンテキストデータを保持することはできません。

例えば、下記の async await を利用した非同期処理のコードは、3回スレッドが切り替わります。

async-await-sample.ts

main('<0001>');
main('<0002>');

async function main(context: string) {
    await async_log('Hello', context);
    await async_log('World', context);
    await async_log('!', context);
}

async function async_log(msg: string, context: string) {
    console.log(context, msg);
}


<0001> Hello
<0002> Hello
<0001> World
<0002> World
<0001> !
<0002> !

コンテキストデータを引き継ぐには、メソッドの引数で渡す必要があります。

zone.jsではコンテキストデータをzoneで引き継ぐ

zone.jsでは、zoneという概念があります。zoneとは、非同期処理を管理することができる単位です。zoneの中で、setTimeout や setInterval , Promise などで、非同期で呼び出すメソッドを登録すると、そのメソッドもzoneの中で実行されます。

例えば、下記のコードを見てください。

zone-sample.ts
/// <reference path="typings/node/node" />
/// <reference path="typings/zone.js/zone.js.d.ts" />

// zone.js を読み込む
require('zone.js');

// zoneを作る
Zone.current.fork({
    name: 'zone1',
    properties: {
        context:'<0001>'
    }
}).run(() => {
    main();
});

// zoneを作る
Zone.current.fork({
    name: 'zone2',
    properties: {
        context:'<0002>'
    }
}).run(() => {
    main();
});

async function main() {
    await async_log('Hello');
    await async_log('World');
    await async_log('!');
}

async function async_log(msg: string) {
    console.log(Zone.current.get('context'), msg);
}
<0001> Hello
<0002> Hello
<0001> World
<0002> World
<0001> !
<0002> !

zoneの中でmainメソッドを実行することで、zoneのpropertiesに登録したコンテキストデータを、async_logメソッドの中で参照することができます。なので、mainメソッドで、コンテキストデータを引き継ぐ必要がなく、コンテキストデータとメソッドの引数をキレイに分離することができます。

async await のエラーハンドリング

async await を利用すると非同期処理をコード上は同期処理のように実装することができます。しかし、エラーハンドリングで問題があります。

問題のコード
main();

async function main() {
    await async_error('async error');
}

async function async_error(msg: string) {
    throw new Error(msg);
}

このコードを実行すると、エラーメッセージもなくプログラムは終了します。このエラーメッセージを取得するには、try-catchで例外をハンドリングする必要があります。

修正したコード
main();

async function main() {
    try {
        await async_error('async error');
    } catch (e) {
        console.error(e.stack);
    }
}

async function async_error(msg: string) {
    throw new Error(msg);
}

zone.jsによる async await のエラーハンドリング

zone.jsでは、zoneの中で発生した例外をハンドリングすることができます。例外のハンドリングを行うには、onHandleErrorを利用します。

zone.jsによるエラーハンドリング
/// <reference path="typings/node/node" />
/// <reference path="typings/zone.js/zone.js.d.ts" />

require('zone.js');

Zone.current.fork({
    name: 'zone1',
    onHandleError: (delegate, current, target, error) => {
        console.error(error.rejection.stack);
        return false;
    }
}).run(() => {
    main();
});

async function main() {
    await async_error('async error');
}

async function async_error(msg: string) {
    throw new Error(msg);
}

zone.jsを使うことで、mainメソッドに手を加えずにエラーハンドリングを実装することができます。

まとめ

zone.jsを利用することで、zone単位に非同期処理を実装することができます。zoneは、裏で仕込むことができるため、zone.jsを利用することで様々なミドルウェアを作ることができます。

試しに、zone.jsのエラーハンドリングをの仕組みを使って、jasmineでasync-awaitのテストコードを書くためのユーティリティのasync-await-jasmineを作ってみました。

他にもいろいろと便利なミドルウェアを作ることができると思います。

<おまけ> zone.jsのtsdについて

2016/3/6時点では、tsdで取得できるzone.jsの型定義が0.5以前のものになっています。0.6以上のzone.jsの型定義ファイルは、node_modules/zone.js/dist/zone.js.d.tsにありますので、コピーして利用してください。

67
54
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
67
54