JavaScriptには Promise
という非同期処理のための仕組みがありますが、
通常のPromiseはresolve/rejectをPromiseコンストラクタの中でしか呼べないという制約があります。
ですが、Supabaseのコードベースではそれを柔軟に扱うために、Deferred
クラスという仕組みが使われています。
Deferredクラスの実装
export class Deferred<T = any> {
public static promiseConstructor: PromiseConstructor = Promise
public readonly promise!: PromiseLike<T>
public readonly resolve!: (value?: T | PromiseLike<T>) => void
public readonly reject!: (reason?: any) => any
public constructor() {
;(this as any).promise = new Deferred.promiseConstructor((res, rej) => {
;(this as any).resolve = res
;(this as any).reject = rej
})
}
}
何が嬉しいのか?
通常の Promise の使い方はこうです:
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done"), 1000)
})
この場合、resolve/rejectを外部から呼ぶことはできません。
一方で Deferred を使うと、後から明示的に resolve や reject を呼び出すことが可能になります:
const deferred = new Deferred<string>()
setTimeout(() => {
deferred.resolve("OK!") // 外からresolveできる!
}, 1000)
await deferred.promise // => "OK!"
なぜ便利なのか?
Deferredパターンは以下のような場面で非常に役立ちます:
-
コールバックベースのAPIをPromiseに変換したいとき
-
複数イベントの完了を待ちたいとき
-
一度だけ解決されるフラグ的な非同期処理が欲しいとき(例:ロックの開放)
-
await で外から制御可能なPromiseを生成したいとき
Supabaseでの利用例
SupabaseのSDKでは、ロック機構や状態遷移の管理、非同期イベントの通知など、
Promiseの解決タイミングを後回しにする必要がある場面でこの Deferred が使われています。
まとめ
- Deferred は Promise をより柔軟に制御するための仕組み
- resolve() や reject() を 外部から呼べる
- Supabaseのような高機能ライブラリでは非同期制御の要となっている