spotAsync<T> static method

Future<T> spotAsync<T>({
  1. String? name,
})

Resolves and returns an async singleton of type T.

Use this for services registered with registerAsync that require asynchronous initialization. The instance is created once and cached.

Can also be used to resolve regular singletons asynchronously.

Type Parameters:

  • T: The type to resolve (must be registered)

Parameters:

  • name: Optional name qualifier for named instances

Returns: Future<T> that resolves to the instance

Throws:

Example:

// Resolve async singleton
final db = await Spot.spotAsync<Database>();
final api = await spotAsync<IApiClient>();

// Named instance
final remoteCache = await spotAsync<Cache>(name: 'remote');

// In async context
Future<void> initApp() async {
  final db = await spotAsync<Database>();
  await db.loadInitialData();
}

// Multiple async dependencies
final results = await Future.wait([
  spotAsync<Database>(),
  spotAsync<IApiClient>(),
  spotAsync<IAuthService>(),
]);

See also:

Implementation

static Future<T> spotAsync<T>({String? name}) async {
  final key = SpotKey<T>(T, name);

  // Fast path: check singleton cache first for performance
  if (_singletonCache.containsKey(key)) {
    if (logging) log.v('Cache hit for async $key');
    return _singletonCache[key] as T;
  }

  if (!registry.containsKey(key)) {
    final registeredTypes = registry.keys.map((k) => k.toString()).join(', ');
    throw SpotException(
      'Type $key is not registered in Spot container.\n'
      'Registered types: ${registeredTypes.isNotEmpty ? registeredTypes : '(none)'}\n\n'
      'Did you forget to register it in SpotModule or with Spot.registerAsync()?'
    );
  }

  // Check for circular dependency
  if (_resolutionStack.contains(key)) {
    final cycle = [..._resolutionStack, key].map((k) => k.toString()).join(' -> ');
    throw SpotException(
      'Circular dependency detected: $cycle\n'
      'Cannot resolve $key because it depends on itself (directly or indirectly).'
    );
  }

  _resolutionStack.add(key);

  try {
    if (logging) log.v('Async injecting $key -> ${registry[key]!.targetType}');

    final service = registry[key]!;
    final instance = await service.locateAsync();
    if (instance == null) {
      throw SpotException('Class $key resolved to null');
    }

    // Cache initialized async singletons for faster subsequent access
    if (service.type == SpotType.asyncSingleton && service.instance != null) {
      _singletonCache[key] = instance;
      if (logging) log.v('Cached async singleton $key');
    }

    return instance;
  } catch (e) {
    if (e is SpotException) {
      rethrow;  // Re-throw SpotException as-is
    }
    log.e('Failed to async locate class $key', e);
    throw SpotException('Failed to async resolve $key: ${e.toString()}');
  } finally {
    _resolutionStack.removeLast();
  }
}