Skip to content

Commit

Permalink
#22 - ReadyOrNotMixin documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
turn-a-round committed May 10, 2020
1 parent e0a5830 commit f0c689c
Showing 1 changed file with 72 additions and 11 deletions.
83 changes: 72 additions & 11 deletions src/sprightly/lib/utils/ready_or_not.dart
Original file line number Diff line number Diff line change
@@ -1,31 +1,92 @@
import 'dart:async';

/// SIngleton async (or not) function type
typedef FutureOr<T> ReadyOrNotWorker<T>();

/// Helper mixin to help defining a singleton function execution.
///
/// dart has no provision for async constructor. In case we need to execute an
/// async function before a class is actually usable (e.g. get data from web
/// through an async http get call & then parse the data & provide data). The
/// process complicates even further when the class is used with singleton
/// instance. The outside world would not have the intimation if such functions
/// have already been invoked or is currently under process. That's a awful lot
/// of code & messy state handling.
///
/// Example:
/// ```dart
/// /// A class designed for singleton use
/// class SomeClass with ReadyOrNotMixin {
/// static SomeClass _cache = SomeClass._();
///
/// // constructor
/// SomeClass._() {
/// // **attach the helper executer**
/// getReadyWorker = _initialize; // can be used with `super.` prefix, too
/// }
///
/// // factory to implement singleton instance
/// factory SomeClass() => _cache;
///
/// // this private value is set only if the [_initialize] function complete successfully
/// Object _asyncObject;
///
/// // thus, this publicly accessible [value] depends on the successful execution
/// // of [_initialize] & it only needs to happen once
/// Object get value => _asyncObject;
///
/// // the async job that need to be executed in singleton manner
/// Future _initialize() async {
/// _asyncObject = await someAsyncJob();
/// }
/// }
///
/// void main() async {
/// SomeClass someClass = SomeClass();
///
/// // either check before running
/// if (!someClass.ready) await someClass.getReady();
/// // or, it doesn't matter if someone has already run it or it's in progress
/// await someClass.getReady();
///
/// // now we're safe to use
/// someClass.value;
/// }
/// ```
mixin ReadyOrNotMixin<T> {
bool _initialized = false;
bool _working = false;
FutureOr<T> _future;

/// Registers the [getReady] singleton job
ReadyOrNotWorker<T> getReadyWorker;

/// Register additional singleton jobs
final Map<String, ReadyOrNotWorker> additionalSingleJobs = {};
final Set<String> _workingJobs = Set<String>();
final Map<String, FutureOr> _workingFutures = {};

/// Whether the [getReady] has been executed ***once*** successfully or not
bool get ready => _initialized;

/// The singleton executor of [getReadyWorker]
FutureOr getReady() async {
if (!_initialized && !_working) {
_working = true;
try {
_future = getReadyWorker();
await _future;
_initialized = true;
} finally {
_future = null;
_working = false;
}
} else if (_working) await _future;
if (null != getReadyWorker) {
if (!_initialized && !_working) {
_working = true;
try {
_future = getReadyWorker();
await _future;
_initialized = true;
} finally {
_future = null;
_working = false;
}
} else if (_working) await _future;
}
}

/// The singleton executor of [additionalSingleJobs]
FutureOr<R> triggerJob<R>(String jobName, {bool onReady = true}) async {
var shouldProceed = additionalSingleJobs.containsKey(jobName);
if (shouldProceed) {
Expand Down

0 comments on commit f0c689c

Please sign in to comment.