Created
March 29, 2026 15:38
-
-
Save nrbnlulu/96b776e50b27d71bb6c0123a8a2bea58 to your computer and use it in GitHub Desktop.
dart dataloader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'dart:async'; | |
| typedef LoaderFn<K, T, E> = Future<Map<K, Result<T, E>>> Function(List<K> keys); | |
| class DataloaderConfig { | |
| const DataloaderConfig({ | |
| this.maxBatchSize = 100, | |
| this.delayBeforeResolve = const Duration(milliseconds: 100), | |
| }); | |
| final int maxBatchSize; | |
| final Duration delayBeforeResolve; | |
| } | |
| class Dataloader<K, T, E extends Object> { | |
| Dataloader({ | |
| required LoaderFn<K, T, E> loaderFn, | |
| DataloaderConfig config = const DataloaderConfig(), | |
| }) : _loaderFn = loaderFn, | |
| _config = config; | |
| final LoaderFn<K, T, E> _loaderFn; | |
| final DataloaderConfig _config; | |
| final Map<K, Completer<Result<T, Object>>> _unresolvedFutures = {}; | |
| final Map<K, Result<T, Object>> _cache = {}; | |
| Timer? _resolveSchedulerTimer; | |
| static Result<T, Object> _liftErr<T, E>(Result<T, E> r) => switch (r) { | |
| Ok<T, E>(:final data) => Ok<T, Object>(data), | |
| Err<T, E>(:final error) => Err<T, Object>(error!), | |
| }; | |
| Future<void> _resolveBatch() async { | |
| final keys = List<K>.from(_unresolvedFutures.keys); | |
| if (keys.isEmpty) return; | |
| try { | |
| final results = await _loaderFn(keys); | |
| for (final entry in results.entries) { | |
| final result = _liftErr(entry.value); | |
| _cache[entry.key] = result; | |
| _unresolvedFutures.remove(entry.key)?.complete(result); | |
| } | |
| for (final key in List<K>.from(_unresolvedFutures.keys)) { | |
| _unresolvedFutures.remove(key)?.complete(const Err('not found')); | |
| } | |
| } catch (e) { | |
| for (final key in keys) { | |
| _unresolvedFutures.remove(key)?.completeError(e); | |
| } | |
| } | |
| } | |
| void _resetResolveScheduler() { | |
| _resolveSchedulerTimer?.cancel(); | |
| _resolveSchedulerTimer = Timer(_config.delayBeforeResolve, _resolveBatch); | |
| } | |
| Future<Result<T, Object>> _load(K key) { | |
| final cached = _cache[key]; | |
| if (cached != null) return Future.value(cached); | |
| if (_unresolvedFutures.containsKey(key)) { | |
| _resetResolveScheduler(); | |
| return _unresolvedFutures[key]!.future; | |
| } | |
| final completer = Completer<Result<T, Object>>(); | |
| _unresolvedFutures[key] = completer; | |
| if (_unresolvedFutures.length > _config.maxBatchSize) { | |
| _resolveSchedulerTimer?.cancel(); | |
| _resolveBatch(); | |
| } else { | |
| _resetResolveScheduler(); | |
| } | |
| return completer.future; | |
| } | |
| Future<T?> loadOpt(K? key) async { | |
| if (key == null) return null; | |
| try { | |
| return (await _load(key)).ok; | |
| } catch (_) { | |
| return null; | |
| } | |
| } | |
| Future<T> load(K key) async { | |
| return switch (await _load(key)) { | |
| Ok<T, Object>(:final data) => data, | |
| Err<T, Object>(:final error) => throw StateError('Dataloader key not found: $error'), | |
| }; | |
| } | |
| void clearCache(K key) => _cache.remove(key); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment