await をトリガに開始する Promise

fastify の inject 経由で light-my-request を使っていて、ついうっかり次のように書いてしまい、

await fastify
    .inject()
    .get('/')
    .headers({ foo: 'bar' })
    .query({ foo: 'bar' })

おっと .end() が抜けてるじゃん、と思ったのですがこれでも通っており、どういうことかと思って light-my-request のコードを見てみました。

https://github.com/fastify/light-my-request/blob/b7c89c97fd687b53aff3e5a0ddece2e2086ef634/index.js#L169C55-L181

Object.getOwnPropertyNames(Promise.prototype).forEach(method => {
  if (method === 'constructor') return
  Chain.prototype[method] = function (...args) {
    if (!this._promise) {
      if (this._hasInvoked === true) {
        throw new Error(errorMessage)
      }
      this._hasInvoked = true
      this._promise = doInject(this.dispatch, this.option)
    }
    return this._promise[method](...args)
  }
})

なるほど Promise をラップして Promise 関係のメソッドの呼び出しで実行していました。await で .then() が呼ばれるのでその時点で開始されるということですね。

自前で実装するとこんな感じでしょうか、

import { setTimeout } from "timers/promises";

class MyPromise<T> implements PromiseLike<T> {
    private promise: Promise<T> | null = null;

    constructor(
        private readonly executor: ConstructorParameters<typeof Promise<T>>[0],
    ) {}

    then<TResult1 = T, TResult2 = never>(
        onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
        onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
    ): PromiseLike<TResult1 | TResult2> {
        if (!this.promise) {
            this.promise = new Promise(this.executor);
        }
        return this.promise.then(onfulfilled, onrejected);
    }
}

(async () => {
    const p = new MyPromise((resolve)=>{
        console.log("a");
        process.nextTick(() => {
            resolve(null);
        });
    })
    await setTimeout(100);
    console.log("z");
    await p;
})();

MyPromise の中身は await するまで開始しないので z -> a の順番で表示されます。 素の Promise だと new 時点で中身が開始するので a -> z の順番になります。