Native async/await 

今日はZone.jsがどうやって`async/await`を監視することを紹介したいと思います。

Zone.jsがJavascriptの非同期の操作を監視する役割です、例えば`setTimeout`, `Promise.then`など、 でも一つ監視できない非同期の操作があって、それがES2017の`async/await` のことです。native `async/await`がJavascriptの`Promise`を経由しなくて、nativeの`Promise`あるいは直接`MicrotaskQueue`にスケジューリングしましたので、Javascript側で探知できないです。なので、今までAngularがずっと`ES2015`をターゲットとしてコンパイルすることになって、`async/await`を下記のようにJavascriptの`Promise`にTranspileすることになります。

```typescript
function compute(a) {
  return new Promise(res => setTimeout(() => {
    res(a + 1);
  }));
}

async function testAsync() {
  let a = 0;
  console.log('before await', a);
  a = await compute(a);
  console.log('after first await', a);
}
```

が`Generator`と`Promise`に変更しました。

```typescript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};

function compute(a) {
  return new Promise(res => setTimeout(() => {
    res(a + 1);
  }));
}

function testAsync() {
    return __awaiter(this, void 0, void 0, function* () {
        let a = 0;
        console.log('before await', a);
        a = yield compute(a);
        console.log('after first await', a);
    });
}
```

そしたら、下記のコードが正しい結果（Zone）が出力できるようになります。

```typescript
async function testAsync() {
  let a = 0;
  a++;
  console.log('before await', Zone.current.name); // before await zoneA
  a = await compute(a);
  a++;
  console.log('after first await', a, Zone.current.name);  // after first await zoneA
  a = await compute(a);
  a++;
  console.log('after second await', a, Zone.current.name); // after second await zoneA
}

Zone.current.fork({name: 'zoneA'}).run(testAsync);
```

このソリューションがいくつディメリットがあります。

native `async/await` と比べて、
1. バンドルサイズが大きい
2. 性能が遅い
3. Chrome で提供されている新しい[Async Stack Traces](https://v8.dev/docs/stack-trace-api#async-stack-traces) が使えなくなります。
　
なので、Zone.jsがどうやってnative `async/await`　をサポートすることがずっと課題になります。今年Google 内部でBuildシステムに対して、いろいろな整理とかが実施されているらしくて、AngularチームがZone.jsの`async/await`問題にたいして、いろいろ解決のソリューションを探しました、今日はこれらのソリューションを簡単に説明します。

目標としては、

1. できれば、native `async/await` を利用して、JavascriptのPromiseを利用しないこと。
2. `await` のあとで、正しく`await`の前のZoneに戻ること。
3. `await` の後で、正しくもう一回`Zone.onInvoke()` hookを実行すること。
4. `fakeAsync()` テストを正しくサポートすること。


になります、すみません、上記の要件を細かく説明したら、すごく長くなりますので、ほかのPostで説明させていただきます。そしたら、いくつのActionを実施しました。

1. 可能のソリューションを探します。
2. 各ソリューションにたいして、
    - 実装難易度
    - 性能
    - Buildツールの連携

を評価すること。

## ソリューション１、`async/await` -> `Generator` にTransformすること。

```typescript
function compute(a) {
  return a + 1;
}

async function testAsync() {
  let a = 0;
  a++;
  console.log('before await', a, Zone.current.name);
  a = await compute(a);
  a++;
  console.log('after first await', a, Zone.current.name);
  a = await compute(a);
  a++;
  console.log('after second await', a, Zone.current.name);
}
```

が下記のコードになりました。

```typescript
function testAsync_generator_1* () {
  let a = 0;
  a++;
  console.log('before await', a, Zone.current.name);
  a = yield compute(a);
  a++;
  console.log('after first await', a, Zone.current.name);
  a = yield compute(a);
  a++;
  console.log('after second await', a, Zone.current.name);
}

function testAsync() {
  return Zone.__awaiter(this, [], testAsync_generator_1);
}
```

そして、`Zone.__awaiter()`が下記のようになりました。

```typescript
Zone.__awaiter = async function<T>(thisArg: any, _arguments: any, generator: () => Generator<T>) {
  const zone = Zone.current;
  const gen = generator.apply(thisArg, _arguments);
  let res = gen.next();
  const args = [await res.value];
  while (!res.done) {
    try {
      res = zone.run(gen.next, gen, args, 'native await');
      args[0] = await res.value;
    } catch (error) {
      zone.runGuarded(() => {
        res = gen.throw(error);
      }, undefined, [], 'native await');
    }
  }
  return args[0];
}
```

簡単に言えば、Javascript `Promise` を使わなくて、`Generator` を`iterator`としてロープして、`await` で実装する方法です。

- メリットは：Javascript `Promise`がなくなる。
- ディメリットは：Transformのロジックが難しくなる、そして、ES2018の`async generator` を対応するためにはべつのTransformを実装しないといけないです。
- 性能：JavascriptのPromiseと大きい差がありません。
- Debug Experience: SourceMapが有効する場合、native `async/await`, Javascript PromiseとこのGeneratorのソリューションが同じ


## ソリューション２：`await` 前後`beforeAwait()`, `afterAwait()`を追加してTransformして、Zoneで新しい`onBeforeAwait()`, `onAfterAwait()` hook で実装すること。

```typescript
async function testAsync() {
  console.log("async begin");
  await 1;
  console.log('after await 1');
  await asyncFunc1(); 
  console.log('after await asyncFunc1');
  await asyncFunc2();
  console.log('after await asyncFunc2');
  console.log("async end");
}
```

が下記のコードになりました。

```typescript
async function testAsyncTranspiled() {
  let taskSignal: any;
  try {
    beforeAsync();

    taskSignal = beforeAwait();
    await 1;
    afterAwait(taskSignal);
    console.log('after await 1');


    taskSignal = beforeAwait();
    await asyncFunc1();
    afterAwait(taskSignal);
    console.log('after await asyncFunc1');

    taskSignal = beforeAwait();
    await asyncFunc2();
    afterAwait(taskSignal);
    console.log('after await asyncFunc2');

    afterAsync();
  } catch (err) {
    awaitError(err);
  } finally {
    afterAwait(taskSignal);
  }
}
```

になりました。そして、`beforeAwait()`, `afterAwait()` が下記のような感じです。

```typescript
function beforeAsync() {
  Zone.current.onBeforeAwait();
}
function afterAsync() {
  Zone.current.onAfterAwait();
}
function beforeAwait() {
  Zone.current.onAfterAwait();
  Zone.captureZone(Zone.current);
  return Zone.current;
}
function afterAwait(taskSignal: any) {
  Zone.restoreZone(taskSignal);
  Zone.current.onBeforeAwait();
}

function awaitError(err: any) {
  Zone.current.onHandleError(err);
}
```

今Google内部でこのようなイメージの方法でZone.jsをES2017で対応しているらしいです。
このソリューションに対して、
- メリット：Transformの実装が簡単
- ディメリット：Zoneが新しいAPIを提供必要、一部自分のZoneSpecを実装するユーザに対して、BreakingChangesになります。
- 性能がNative と同じ
- Debug ExperienceもNativeと同じ

### まとめ

今このプロジェクトが一旦終了で、Angular v14とか以降再開する予定があります、理由が：
- 今Angular CLIですでにasync/await だけJavascript PromiseにDownlevelすることを実装しましたので、開発者としては特に不便がない。
- いろいろテストして、性能的にはJavascript Promiseにも遅くない、Native `async/await`と比べて差別とても小さい。
- Debug Experienceに対して、Javascript PromiseでもSourceMapと連携して、Nativeと同じDebug Experienceを提供できる
- Bundle sizeで、Javascript Promise の__awaiterが共通のfunctionを呼び出すだけですので、ここにも問題ないです。

なので、今問題になりそうのは、`Async Stack Traces`のサポートのことになります。なので、このプロジェクトの優先度が低くなって、これから`Zoneless`の設計とともに、`before/after` の案ですすめる可能性が高いです。

まだ情報があったら、報告させていただきます。
どうもありがとうございました。