ReactのuseがPromiseの結果を同期的に取得する方法
use()に対してすでにResolve済みのPromiseを渡した場合、DOMへのコミットは1回しか行われない
fallbackしたときとresolve後で2回コミットされそうに思えるが、1回しか行われない
もちろん1回目のコミットはないほうが良い
すぐにresolve後の内容で置き換えられるので単純に無駄
画面がちらつく原因にもなる
Reactのレンダリングは同期的なので、awaitとかしている訳ではない
答えは↓のRFCに書いてある
主に2つの対策がある
Promiseオブジェクトに状態を示すプロパティを追加する
こっちで対処できたほうがレンダリング回数が1回になるので効率は良い
microtaskのflushを待つ
Promiseオブジェクトに状態を示すプロパティを追加する
こんな感じ
code: ts
type Thenable<T> = Promise<T> & {
status: "pending" | "fulfilled" | "rejected";
value?: T;
reason?: unknown;
}
useにPromiseを渡すと、そのPromiseにはstatusプロパティが追加される
then()でstatus: "fulfilledとvalueのセット、catch()でstatus: "rejected"とreasonのセットがされる具合
Promiseのprototypeを書き換えたりする訳ではない
プロパティは当然同期的にアクセス可能なので、これで同期的に状態を取得できる
RFCだけでなく実装もそうなっているっぽい
elecdeer.iconはマジ?と思った
hookと同じような仕組みでWaekMapとかで保持したりしないんだ?
が、これはちゃんとRFCに理由が書いてある
Although this convention is not part of the JavaScript specification, we think it's a reasonable way to track a promise's result. The ideal is that the lifetime of the resolved value corresponds to the lifetime of the promise object. The most straightforward way to implement this is by adding a property directly to the promise.
An alternative would be to use a WeakMap, which offers similar benefits. The advantage of using a property instead of a WeakMap is that other frameworks besides React can access these fields, too.
Promiseオブジェクトのライフサイクルに追従する最も簡単な実装である
WeakMapだとサードパーティのライブラリが、Promiseに対して既にResolve済みだとマークすることができない
プロパティを生やすだけであれば、この規約を知っていればuseに渡す前にマークができる
確かに...
microtaskのflushを待つ
すごい雑に言うと、ECMAScriptにはpromise.then(cb)はsetTimeout(cb, 0)よりも早くcbが呼ばれるという仕様がある
前者がマイクロタスクと呼ばれ、マイクロタスクのキューが全て捌けてからでないと他のタスクキューが実行されない
詳しくはこのへん
setTimeout(cb, 0)のタイミングだと、resolve済みのPromiseに対するthen()が既に実行されていることが保証されているので、そのタイミングでチェックをすれば、コミットする前に再レンダリングをトリガーすることができる
elecdeer.icon これはまあそうだね