JavaScriptの ~. 構文って知ってる？　Promise Pipeliningが拓く非同期処理の未来

PromiseはES2015からJavaScriptに導入された機能で、非同期処理をいい感じに記述できるたいへんありがたいオブジェクトです。実は、Promiseの強化版ともいえる新機能、その名も`HandledPromise`が提案されています。また、この`HandledPromise`のための新構文`~.`も同時に提案されています。

例えば、`~.`を用いて次のようなプログラムを書くことができます。

```js
const data = await disk~.openDirectory('foo')~.openFile('bar.txt')~.read();
```

この記事では、`HandledPromise`と`~.`について概説します。例によって、これらは**Stage 1プロポーザル**です。つまり、「こういうのがあってもいいんじゃない？」と思われている段階であり、具体的な方向性とかは何一つ決まっていないということです。この記事でお伝えするのは現段階での構想であり、当然ながらまだJavaScriptに正式採用されたものではありません。何年後かにこの機能が採用されたときに全然別の見た目になっていたとしても、悪しからずご了承ください。

この記事は以下の2つのプロポーザルを基に書かれています。

- [HandledPromise](https://github.com/tc39/proposal-eventual-send)
- [proposal-wavy-dot](https://github.com/tc39/proposal-wavy-dot)

また、このPolyfillも参考になるでしょう。

- https://www.npmjs.com/package/@agoric/eventual-send

## `HandledPromise`の意義

ざっくりと言えば、`HandledPromise`は**効率的なRPCを実装するための標準化された機構**です[^standardized]。RPCというのはRemote Procedure Callのことですね。要するに、通信先に存在する（仮想的な）オブジェクトに対して操作を行うことができるのです。このプロポーザルでは、タイトルにある`~.`という構文をRPCを行うための統一的なものとして提供します。

`HandledPromise`は`~.`をサポートする特殊なPromiseであり、RPCをサポートするライブラリの作者は、`~.`をサポートするために`HandledPromise`を用いた実装を行うことになります。

[^standardized]: 実際はまだStage 1なので、現在標準化の途上にあると言うべきですが。

ですので、ライブラリのユーザーは基本的に`~.`構文を用いればよく、ライブラリを作る側は`HandledPromise`を使って裏で頑張ると言う構図になっています。

これは、我々は普段`async`関数と`await`があればなんとかなる一方で、ライブラリの中では`new Promise`によるPromiseの作成のような多少トリッキーなことが行われているのと似ています。

### `HandledPromise`が何を解決するのか

なぜ`HandledPromise`が必要なのかという点は上述のプロポーザル文書を読んでもいまいちピンと来ないと思いますが、添付されている[スライド](https://github.com/tc39/agendas/blob/master/2019/10.eventual-send-as-recorded.pdf)は具体例などもあり比較的分かりやすいです。スライドの10ページ目を引用します。

<img width="713" alt="スクリーンショット 2020-01-04 13.26.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233667/2e6e18ed-a5cc-3650-c3c6-f16f0c3d4e3c.png">

コードで例示するとこういう感じです。画像左の従来型の処理は以下のようなコードに対応します。

```js
const dir = await disk.openDirectory('foo');
const file = await dir.openFile('bar.txt');
const data = await file.read();
```

これは何かのファイルシステムからデータを読む処理に見えます。見てわかるとおり、`await`が直列に3つ並んでいます。もしファイルシステムがネットワークの向こうにあった場合、この処理は必然的に3RTTの時間がかかることになります。

`HandledPromise`を使うことで、この処理は`disk~.openDirectory('foo')~.openFile('bar.txt')~.read()`と書けるようになります。

一見すると`~.`が「`await`してメソッド呼び出し」という構文に見えなくもありませんが、もっと本質的な違いがあります[^difference]。実は、こう書くことでかかる時間がほぼ1RTTで済むのです。その理由は、一つの命令の結果を受け取る（命令のPromiseが解決される）より前に次の命令を発行できるからなのです。今回の場合、`openDirectory`の結果を受け取る前に`openFile`や`read`という命令を送信しています。`~.`という構文は、この「Promiseが解決されていなくても次の命令を発行する」という処理のための構文です。

重要なのは、たとえば`disk~.openDirectory('foo')`自体がひとつの(Handled)Promiseであるという点です。これを`await`すればディレクトリを表すオブジェクトが手に入るでしょう。それにも関わらず、`~.`を用いて（Promiseとして解決されるより前に）に追加の命令を発行できるのがポイントです。この、「Promiseである」という点と「`~.`を通じてメソッド呼び出し（など）ができる」という2つの特徴を両立しているのが`HandledPromise`の本質的な特徴です。

[^difference]: 厳密には、その違いは`disk`などの実装に内包されているのであり`~.`を見ただけで一目瞭然というわけではないのですが。

しかし、勘のいい皆さんはすでにお気づきでしょう。これが非同期処理のレイテンシに対する銀の弾丸などではなく、まとめて送られてくる命令を正しく処理するという複雑さがネットワークの向こう側に追いやられただけであるということに……。

まあ、パフォーマンスのためなら背に腹は代えられません。ネットワークの向こう側は適当に頑張ってもらいつつ、少なくともこちら側をうまくやるための道具を導入するというのが`HandledPromise`の趣旨です。ネットワークの向こう側に複雑さを追いやるのはGraphQLとかにも見える流れですし、それが今どきのやり方なのでしょう。`HandledPromise`にもいい感じに対応したサーバー側のフレームワークがきっと出るでしょう。

記事タイトルにあるPromise Pipeliningとは、非同期処理の結果が帰って来る前に次々と命令を送ってレイテンシの低減を図ることを指すようです。

以上で`HandledPromise`がやりたいことは見えましたね。前述の二面性を持ったオブジェクトを言語標準として提供することで、RPCなどをやるときのための統一されたインターフェース（あとついでに構文も）を提供しようという魂胆です。ここからは、これを実現するためにどのような機構が必要だったのかを見ていきます。実際のところ、本質的な難しさはネットワークの向こうに押しやっているので（というより、様々な種類の非同期/分散処理に対する抽象化を与えるためにはそうするしかないのですが）、`HandledPromise`の機構は一度知ってしまえばそこまで複雑怪奇なものではありません。

## `HandledPromise`とハンドラ

`HandledPromise`を理解するにあたって重要な概念が**ハンドラ**です。ハンドラと言えば[Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)を扱うときに出てくる用語でしたが、ここでいうハンドラもそれと似た雰囲気です。実際、ある意味で`HandledPromise`は`Proxy`に近い側面を持っています。

`HandledPromise`におけるハンドラは、3種類の処理に対する処理を記述する関数です。3種類の処理とは、**プロパティアクセス**、**関数として呼び出す**、**メソッド呼び出し**の3種類です。この3種類は、ちょうど`~.`で記述できる以下の3種類の構文に対応しています。`p`というのはHandledPromiseです（後述しますが、普通のPromiseに対しても動作はします）。

| 処理 | 構文 |
|:----|:-----|
| プロパティアクセス | `p~.foo` / `p~.[name]` |
| 関数として呼び出す | `p~.(...args)` |
| メソッド呼び出し | `p~.method(...args)` / `p~.[name](...args)` |

余談ですが、これらの構文は[optional chaining](https://qiita.com/uhyo/items/6cd88c0ea4dc6289387a)の構文に非常に似ていますね。

上記の`disk~.openDirectory('foo')~.openFile('bar.txt')~.read()`という例では、メソッド呼び出しの構文が3連続で続いていたことになります。`.`を`~.`に変えることで、これはこの場で行われているのではなくて実際はRPCなんですよと言う雰囲気を出しています。

そして、ハンドラとはこの3種類の操作が行われたときの処理を指定できるオブジェクトです。3種類の操作を全部記述するとこのようになります。

```js
{
  get(target, name) {
    // target~.[name] に対応するハンドラ
    // プロパティアクセス結果のPromiseを返す
  },
  applyFunction(target, args) {
    // target~.(...args) に対応するハンドラ
    // 関数呼び出しの返り値のPromiseを返す
  },
  applyMethod(target, name, args) {
    // target~.[name](...args) に対応するハンドラ
    // メソッド呼び出しの返り値のPromiseを返す
  },
}
```

つまるところ`HandledPromise`とは、「ハンドラを装備することによって`~.`に対する挙動をカスタマイズすることができる`Promise`」です。

では、次は`HandledPromise`の作り方を見ましょう。

## `HandledPromise`コンストラクタ

`HandledPromise`は`Promise`の上位互換のような存在で、作り方は`Promise`に少し似ています。

```js
new HandledPromise((resolve, reject, resolveWithPresence) => {
}, unfulfilledHandler)
```

普通の`Promise`に比べて少しごちゃごちゃしていますね。コンストラクタに関数を渡すのは同じですが、`resolve`と`reject`に比べて新しい`resolveWithPresence`という選択肢が加えられています。さらに、関数に加えて`unfulfilledHandler`を渡さなければいけません。

分かりやすいのは`unfulfilledHandler`なので、こちらから解説します。名前の通り、これは`HandledPromise`がまだ成功していないときに使われるハンドラです。とりあえず単純な例を見てみましょう。

```js
(async ()=> {
  const p = new HandledPromise(() => {}, {
    get(target, name) {
      console.log(name);
      return "wow!";
    }
  });

  // p2はPromise。
  // ここで "foo" がコンソールに表示される。
  const p2 = p~.foo;
  const p2Value = await p2;

  // p2Valueは "wow!"
  console.log(p2Value);
})();
```

ここで作った`p`は永遠に解決されない`HandledPromise`です。しかし、`get`に対応したハンドラが与えられています。`p`に対して`~.`で行う操作はこのハンドラが対応することになります。

まず`p~.foo`としています。こうすることで、`p`が持つ`get`ハンドラが呼び出されます。引数`target`は`p`そのもの、`name`は`"foo"`です。注意点として、`~.`による操作の結果は常にPromiseです。プロパティアクセスのような見た目からはちょっと想像しにくい部分もありますが、プロパティの取得もRPCの一貫と考えればまあ納得できるかもしれません。`await p~.foo`のようにまとめれば、「`foo`を取得するという処理を待つ」と言う雰囲気が出ます。

今回は`get`ハンドラが`"wow!"`を返したので、`p2`は`"wow!"`に解決されるPromiseになります。これを`await`することで無事に結果を取得できました。

お分かりとは思いますが、最初の例のように`~.`を何個も繋げたい場合は、これらのハンドラが新しい`HandledPromise`を返すようにすればよいのです。

## `HandledPromise`を解決する

さて、ここからが`HandledPromise`の本領発揮です。というのも、上の例だけなら普通のProxyでも似たようなことができるからです。重要なのは、`HandledPromise`が上記のようなProxy的な側面と、それ自身Promiseであると言う側面を両立できると言う点なのです。

`HandledPromise`自体も`Promise`ですから、それ自身も成功したり失敗することができます。そのとき何が起こるのかを見ていきましょう。

まず、普通のPromiseと同様に`resolve`で解決した場合を考えます。次の例です。

```js
(async ()=> {
  const p = new HandledPromise((resolve, reject) => {
    setTimeout(()=> {
      resolve({
        bar: 12345
      })
    }, 1000);
  }, {
    get(target, name) {
      console.log(name);
      return "wow!";
    }
  });

  const p2Value = await p~.foo;
  console.log(p2Value); // "wow!" が表示される

  await p;

  console.log(await p~.bar); // 12345 が表示される
})();
```

今回、`p`の定義を少し変えて1秒後に`{ bar: 12345 }`というオブジェクトに解決されるようにしました。それより前に`p~.foo`とした場合は従来通り、ハンドラの`get`メソッドが呼ばれます。

では、`await p`として`p`の解決を待った後に`p~.bar`としたら何が起こるでしょうか。上のサンプルに書いてある通り、なんと12345が表示されました。

ここで起こっていることは2段階に分けて説明できます。まず、`p`が成功裏に解決された時点で、`unfulfilledHandler`はお役御免になります。`p`が解決された時点で、これはただのPromiseと同じ扱いになります。したがって、`p~.bar`というのはただのPromiseである`p`に`~.`構文を用いていることになります。

実は、ハンドラを持たないただのPromise（あるいはPromiseですらない値）に`~.`を用いた場合は**デフォルトのハンドラ**が用いられます[^default_handler]。デフォルトのハンドラの挙動では、`p~.bar`というのは`p.then(value => value.bar)`という挙動になります。要するに「awaitしてプロパティアクセス」ということですね。今回は`p`の解決先が`{ bar: 12345 }`だったので、`p~.bar`で`12345`が取得できたことになります。

[^default_handler]: より正確には、`HandldedPromise.resolve`によってデフォルトのハンドラを装備した`HandledPromise`に変換されます。

このデフォルトのハンドラは望ましい場面もあるでしょうが、いつもそうとは限りません。`HandledPromise`が解決した後も`~.`に対する制御を失いたくないこともあるでしょう。

そのような場合、2つの選択肢があります。一つは`resolve`で別の`HandledPromise`に解決すること、そしてもう一つは`resolveWithPresence`を使うことです。

`resolveWithPresence`を使う場合は、解決先を引数で指定する代わりに新しいハンドラオブジェクトを`resolveWithPresence`に渡します。そうすると、自動的に新しいオブジェクトが作られてそのオブジェクトに解決されます。しかも、そのオブジェクトに対して`~.`を用いると指定したハンドラオブジェクトが使用されるのです。なお、presenceとはこの時作られる解決先のオブジェクトのことのようです。なお、この時作られるのは`Object.create(null)`に相当するもので、プロトタイプを持ちません。

```js
(async ()=> {
  const p = new HandledPromise((resolve, reject, resolveWithPresence) => {
    setTimeout(()=> {
      resolveWithPresence({
        get(target, name) {
          return "mom!";
        }
      })
    }, 1000);
  }, {
    get(target, name) {
      console.log(name);
      return "wow!";
    }
  });

  const p2Value = await p~.foo;
  console.log(p2Value); // "wow!" が表示される

  const result = await p;

  console.log(await p~.bar); // "mom!" が表示される
  // {} が表示される
  console.log(result);
})();
```

この例ではまた`p`の定義が変わっており、`resolve`の代わりに`resolveWithPresence`が使われています。今度は`"mom!"`を返す関数が`get`のハンドラとして定義されています。

すると、`p~.foo`や`p~.bar`の挙動が`p`が解決される前後で変わっていることが見て取れます。`p`が解決される前は`unfulfilledHandler`として渡されたものが使われている一方で、解決後は`resolveWithPresence`に渡されたものが使われています。

ちなみに、`resolveWithPresence`は返り値としてたった今作られた解決先のオブジェクトを返します。これにより、このタイミングでオブジェクトをカスタマイズすることが可能となっています。

```js
const presence = resolveWithPresence({
  get(target, name) {
    return "mom!";
  },
});

presence.hello = "world";

// ...

const result = await p;
console.log(result.hello); // "world"
```

以上で`HandledPromise`の機能の解説は終わりました。まだよく分からないという読者の方が多いと思いますが、あとで例を用意しているのでご安心ください。しかしその前に、いくつか補足事項を説明します。

## `~.`の脱糖

`~.`は`HandledPromise`のための新しい構文ですが、実はこれを使わなくても`HandledPromise`の機能は利用可能です。`HandledPromise`コンストラクタの静的メソッドとしていくつかのメソッドが用意されており、具体的には`~.`の構文は以下のように`~.`を使わない形に書き換えることができます。

| 脱糖前 | 脱糖後 |
|:------|:------|
| `p~.foo` | `HandledPromise.get(p, "foo")` |
| `p~.(...args)` | `HandledPromise.applyFunction(p, args)` |
| `p~.method(...args)` | `HandledPromise.applyMethod(p, "method", args)` |

### `E`ヘルパー

上記のような代替手段があるとはいえ、`~.`がないとパイプライニングが少し不便です。例えば、`disk~.openDirectory('foo')~.openFile('bar.txt')`は以下のように書かなければいけません。

```js
HandledPromise.applyMethod(HandledPromise.applyMethod(disk, 'openDirectory', ['foo']), 'openFile', ['bar.txt'])
```

これはちょっと読みにくくて不便ですね。そこで、HandledPromiseのプロポーザルは`E`と言うヘルパーを定義しています。`E`を用いるとこのように書けます。

```js
E(E(disk).openDirectory('foo')).openFile('bar.txt')
```

要するに、`E`でHandledPromiseをラップすることで`~.`と同様のメソッド呼び出し記法がサポートできるというものです。メソッドチェーンをつなぐ時に毎回`E`で囲まないといけないのが残念ですが、`~.`が無ければこれが精一杯というところなのでしょう。

ちなみに、「`E`なんて1文字のグローバル変数を新たに追加して大丈夫なの？」と思った方がいるかもしれませんが、それは心配ありません。`E`は[組み込みモジュール](https://qiita.com/uhyo/items/acd645744fa61c805097)を通じて提供されますから、衝突の心配はありません。

### `~.`とデフォルトハンドラ

上でちらっと説明したように、`~.`の左が`HandledPromise`でなかった場合はデフォルトのハンドラが用いられます。デフォルトのハンドラも、（特に`HandledPromise`ではない普通のPromiseに対して）有効な場合があります。`p`が普通のPromise（かつ`HandledPromise`に解決されていない）だとした場合に、`~.`は次のように動作します。

| 構文 | 動作 |
|:------|:------|
| `p~.foo` | `p.then(obj => obj.foo)` |
| `p~.(...args)` | `p.then(func => func(...args))` |
| `p~.method(...args)` | `p.then(obj => obj.method(...args))` |

いずれも「`await`してからプロパティアクセス/関数呼び出し/メソッド呼び出し」という動作で、このように普通のPromiseに対しては`~.`は「自動`await`」、あるいは（ファンクタの意味での）「map」のように動作します。

これがデフォルトになっているということは、`HandledPromise`についてもこれから大きくかけ離れない挙動が望ましいと思われます。

それはともあれ、`~.`構文が市民権を得てきたら、`HandledPromise`とは関係のない文脈で便利な構文として`~.`が使われることもあるかもしれません。見てもびっくりしないようにしましょう。

## SendOnly系ハンドラ

これまで紹介したハンドラは`get`、`applyFunction`、`applyMethod`という3種類のトラップから成っていました。実は、さらに`getSendOnly`、`applyFunctionSendOnly`、`applyMethodSendOnly`という3種類が存在します。これらは「結果が使われない」ことが分かっているときに使われる特殊なもので、無くても動作はします。たとえば、以下の場合は`p~.method()`の結果が`void`で即座に消されることが分かっているので`p~.method()`の処理には`applyMethod`ではなく`applyMethodSendOnly`が使われます。

```js
void p~.method();
```

他に、ただ単に`p~.method();`と書いた場合も結果が使われないので消せそうですが、この場合どうするかは議論中です（Completion Recordとの兼ね合いがあるため）。

ハンドラを実装する側が、結果が使われないと分かっている場合になんらかの最適化をすることできるかもしれないということでこれらの機能が存在しているようです。

## `HandledPromise`の実践例： 出力に 時間がかかる コンソール

さて、ここからが後半です。この記事の前半では`HandledPromise`のAPIを紹介しました。これだけで読者の皆さんが`HandledPromise`を使いこなせるようになればよいのですが、`HandledPromise`は結構ややこしいためそうもいかないでしょう。

そこで、ここからは`HandledPromise`の実践例を一つ作ってみましたので紹介します。これらの例を通じて`HandledPromise`の理解を自分のものとしましょう。

### 遅延ログ出力の理想的状況

この例のソースコードは以下のCodeSandboxにまとまっています。コードを自分で触ってみたい方はこちらをご参照ください。

- https://codesandbox.io/s/objective-gould-t319h

この例では、画面に何かを出力したいものの、なぜか出力が反映されるまでに時間がかかるという状況を想定しています。具体的にはこんな関数でログを出力しなければいけない場合を考えます。

```js:src/log.js
export const randomSleep = () => {
  const sleepDuration = 200 + Math.floor(Math.random() * 800);
  return new Promise(resolve => {
    setTimeout(resolve, sleepDuration);
  });
};

/**
 * ランダムに遅延した後ログを出力する。
 */
export const log = async (...args) => {
  await randomSleep();
  console.log(...args);
};

```

`log`関数を呼ぶと、ランダムな時間（200ミリ秒〜1000ミリ秒）待ってから`console.log`が呼ばれます。

これを使って3つログを出力しなければならないとすると、最も単純なやり方は次の`main1`関数のようにする方法です。

```js:src/index.js
 async function main1() {
  await log("this is log1");
  await log("log2 is ", 12345);
  await log("Hello, log3!");
}
```

しかし、これはあまりやりたくありません。これだと、3つログを出すのに1つの3倍の時間がかかってしまうからです。

とはいっても、次の`main2`のようにするのもいけません。ログの順番が変わってしまうかもしれないからです。

```js:src/index.js
async function main2() {
  await Promise.all([
    log("this is log1"),
    log("log2 is ", 12345),
    log("Hello, log3!")
  ]);
}
```

では理想的なのはどういう書き方かというと、`~.`を用いて次のように書けると良いですね。この書き方では、それぞれの`log`の処理が終わるのを待たずに次の処理を発行しつつ、`log`の順番は保存されることが期待されます（待ち時間は、前の`log`が終わっていなくても`log`を読んだ瞬間に始まるという想定です）。もちろん、`log()`の結果は(Handled)Promiseなので`await`することでそれまでのログ出力が終了するまで待つことができます。

```js:src/index.js
window.main3 = async function main3() {
  await createLogger()
    ~.log("this is log1")
    ~.log("log2 is ", 12345)
    ~.log("Hello, log3!");
}
```

ということで、このような動作を達成できる`createLogger`を例として作ってみました。もしみなさんが自分で作ることができればおそらく`HandledPromise`マスターと言えるでしょう。やる気のある方はぜひ挑戦してみてください。

なお、残念ながら最初に出てきた`log`関数はそのままだと使えず、もっと`createLogger`と密結合したものにせざるを得ません。もっと実践的にやるならRPCプロトコルから実装していかないといけないのですが、今回はサンプルなのでそこはご容赦ください。

### `createLogger`の実装

ということで、多少長いですが、できたものがこちらです。上述のCodeSandboxを開いている方は、`main3`ボタンを押すことでこいつの動作を確かめられます。何回か押してみると、時間をかけずに、かつ順番通りにログが出力されることが分かります。

```js:src/logger.js
const handler = {
  applyMethod(target, name, args) {
    if (name !== "log") {
      throw new Error(`Undefined method '${name}'`);
    }
    return requestLog(target, args);
  }
};

export const createLogger = () => {
  return new HandledPromise((_rs, _rj, resolveWithPresence) => {
    resolveWithPresence(handler);
  });
};

function requestLog(parent, args) {
  const parentPromise =
    "function" === typeof parent.then ? parent : Promise.resolve();
  return new HandledPromise(async (_rs, _rj, resolveWithPresence) => {
    await randomSleep();
    await parentPromise;
    console.log(...args);
    resolveWithPresence(handler);
  }, handler);
}
```

最初に書いてある`handler`オブジェクトは簡単です。`"log"`という名前のメソッドが呼ばれていたら`requestLog`関数に処理を投げて、他はエラーにします。この`target`に何が渡されるのかというのは、実は場合により異なります。`unfulfilledHandler`の場合（`HandledPromise`がまだ成功裏に解決されていない場合）は`target`は当該の`HandledPromise`になりますが、`resolveWithPresence`の場合（すでに成功裏に解決されている場合）は結果のオブジェクト（presenceオブジェクト）になります。このことは`requestLog`の中で使われています。

`requestLog`の結果（つまりは`p~.log(...)`の結果）は新しい`HandledPromise`です。この`HandledPromise`は、作られたらまず`randomSleep()`で適当な時間待ちます。これが最初の`log`であった遅延の再現です。そして、続けて`parentPromise`を待ちます。これは、言うなれば「前の`log(...)`が終わるまで待つ」ということです。`await randomSleep()`で待っている最中に前の`log(...)`が終わっていた場合は、`await parentPromise`は即座に完了します。これらが終わるとやっと`console.log`を呼び出します。自分の`console.log`が完了したらこの`HandledPromise`が解決されたと見なせるので、`resolveWithPresence`で自身を解決します。

実際のRPCの場合はこの2連続`await`と`console.log`のところが「RPC命令をリモートに送って、返事が来るまで待つ」というような処理になるわけですね。前の`log`が終わるまでこのログを出力しないというような制御は向こうの処理に任せるわけです。

### `createLogger`の特性

`createLogger`の実装はこれだけですが、なかなか面白い特性を見せます。まず、`HandledPromise`がPromiseとProxyの両方の特徴を併せ持つおかげで、`createLogger()`や`log()`の返り値は、続けて`log()`を呼ぶほかにも`await`することができます。今回の実装の場合は`main3`をこのようにすれば、必ずlog1〜log3が出たあとにlog4が出力されます。

```js
async function main3() {
  await createLogger()
    ~.log("this is log1")
    ~.log("log2 is ", 12345)
    ~.log("Hello, log3!");

  console.log("log4");
}
```

また、次のようにすればlog1〜log3とlog4〜log6の二つの系統を混ぜることもできます。なかなか自由度が高いですね。

```js
async function main3() {
  await Promise.all([
    createLogger()
      ~.log("this is log1")
      ~.log("log2 is ", 12345)
      ~.log("Hello, log3!"),
    createLogger()
      ~.log("log4")
      ~.log("log", 5)
      ~.log("log6")
  ]);
}
```

## まとめ

この記事で紹介した`HandledPromise`はステージ1のプロポーザルで、`Promise`としての性質を
保ちつつ、`~.`により追加の操作を受け付けられるという特徴を持つオブジェクトです。特に、まだPromiseとしては解決していなくても、`~.`により矢継ぎ早に命令を送ることができ、その特徴からRPCを実装するのに有用です。記事タイトルにあるPromise Pipeliningとは、まさに結果を待たずに次々非同期処理を進めることを指す言葉です。

`HandledPromise`によって、JavaScriptはこのような非同期処理を表すための新たな語彙を手に入れることになります（プロポーザルが通ればですが）。

ユーザーからすれば`~.`を用いてメソッド呼び出しの形で命令を記述できるのが直感的です。一方で、それを提供するライブラリ作者の側は`HandledPromise`のAPIを用いて結構泥臭い実装をする必要があります。

そもそもこのプロポーザル、「何かいい感じのライブラリができたから標準に提案してみた」という雰囲気があるような気がしないでもありません。ただし、今回説明を省きましたが、PolyfillではPromiseがHandledPromiseに解決されるケースの動作がうまく実装できないという問題があり、そのために標準化を提案しているというストーリーになっています。

`~.`という構文に関しても、このまま進むかどうかは大変怪しいところです。TC39ミーティングの議事録をみても、「**こんな使い所少なそうな機能**のために`~.`というわりと使い道ありそうな構文を使っていいのか」というような議論が見られます。

ということで、たいへん前途多難なプロポーザルで今後どのように進むか全然分かりませんが、なかなか面白そうなプロポーザルなので説明してみました。`~.`という愉快な構文が使えるようになるのを正座して待ちましょう。

### 関連リンク

- [HandledPromise](https://github.com/tc39/proposal-eventual-send)
- [proposal-wavy-dot](https://github.com/tc39/proposal-wavy-dot)
- [@agoric/eventual-send](https://www.npmjs.com/package/@agoric/eventual-send)