useStorage.tsx 1.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. import { useSyncExternalStore } from 'react';
  2. import type { BaseStorage } from '@extension/storage';
  3. type WrappedPromise = ReturnType<typeof wrapPromise>;
  4. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  5. const storageMap: Map<BaseStorage<any>, WrappedPromise> = new Map();
  6. export function useStorage<
  7. Storage extends BaseStorage<Data>,
  8. Data = Storage extends BaseStorage<infer Data> ? Data : unknown,
  9. >(storage: Storage) {
  10. const _data = useSyncExternalStore<Data | null>(storage.subscribe, storage.getSnapshot);
  11. if (!storageMap.has(storage)) {
  12. storageMap.set(storage, wrapPromise(storage.get()));
  13. }
  14. if (_data !== null) {
  15. storageMap.set(storage, { read: () => _data });
  16. }
  17. return (_data ?? storageMap.get(storage)!.read()) as Exclude<Data, PromiseLike<unknown>>;
  18. }
  19. function wrapPromise<R>(promise: Promise<R>) {
  20. let status = 'pending';
  21. let result: R;
  22. const suspender = promise.then(
  23. r => {
  24. status = 'success';
  25. result = r;
  26. },
  27. e => {
  28. status = 'error';
  29. result = e;
  30. },
  31. );
  32. return {
  33. read() {
  34. switch (status) {
  35. case 'pending':
  36. throw suspender;
  37. case 'error':
  38. throw result;
  39. default:
  40. return result;
  41. }
  42. },
  43. };
  44. }