fastify の inject 経由で light-my-request を使っていて、ついうっかり次のように書いてしまい、
await fastify .inject() .get('/') .headers({ foo: 'bar' }) .query({ foo: 'bar' })
おっと .end() が抜けてるじゃん、と思ったのですがこれでも通っており、どういうことかと思って light-my-request のコードを見てみました。
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 の順番になります。