sync* și yield în Dart

O funcție sync* este un generator: produce elementele unui Iterable unul câte unul, cu yield, doar când sunt cerute.

Sintaxă
Iterable<Tip> generator() sync* {
  yield valoare;          // produce un element
  yield* altIterable;     // produce toate elementele altui iterable
}

Ce face sync* și yield

O funcție marcată sync* întoarce un Iterable. În corp, fiecare yield valoare; produce următorul element. Funcția nu rulează toată de la început: pornește când cineva parcurge rezultatul și se oprește la fiecare yield, până când este cerut elementul următor (evaluare leneșă).

Datorită acestui comportament, un generator poate descrie chiar și o secvență infinită (de exemplu toate numerele Fibonacci); iei din ea doar cât îți trebuie cu take sau takeWhile.

yield* produce, pe rând, toate elementele altui Iterable; este util pentru generatoare recursive (parcurgerea unui arbore). Același yield se folosește și în generatoarele asincrone async*, unde produce elementele unui Stream.

Exemple

Exemplul 1: Un generator simplu

Mesajele arată că funcția rulează pas cu pas, intercalat cu bucla care o parcurge.

Iterable<String> zileLucratoare() sync* {
  print('(încep)');
  yield 'luni';
  yield 'marți';
  yield 'miercuri';
  print('(aproape gata)');
  yield 'joi';
  yield 'vineri';
}

void main() {
  for (final zi in zileLucratoare()) {
    print(zi);
  }
}

Rezultat

(încep)
luni
marți
miercuri
(aproape gata)
joi
vineri

Exemplul 2: Secvență infinită, folosită parțial

fibonacci nu se termină niciodată, dar take(10) cere doar primele 10 elemente.

Iterable<int> fibonacci() sync* {
  var a = 0, b = 1;
  while (true) {
    yield a;
    (a, b) = (b, a + b);
  }
}

void main() {
  print(fibonacci().take(10).toList());
  print(fibonacci().where((x) => x.isEven).take(5).toList());
  print(fibonacci().firstWhere((x) => x > 1000));
}

Rezultat

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
[0, 2, 8, 34, 144]
1597

Exemplul 3: yield* pentru un arbore

Generatorul recursiv parcurge toate folderele, la orice adâncime.

class Folder {
  final String nume;
  final List<Folder> subfoldere;
  Folder(this.nume, [this.subfoldere = const []]);

  Iterable<String> toateCaile([String prefix = '']) sync* {
    final cale = '$prefix/$nume';
    yield cale;
    for (final sub in subfoldere) {
      yield* sub.toateCaile(cale);
    }
  }
}

void main() {
  final acasa = Folder('acasa', [
    Folder('documente', [Folder('facturi'), Folder('cv')]),
    Folder('poze', [Folder('vacanta-2025')]),
  ]);
  acasa.toateCaile().forEach(print);
}

Rezultat

/acasa
/acasa/documente
/acasa/documente/facturi
/acasa/documente/cv
/acasa/poze
/acasa/poze/vacanta-2025

Greșeli frecvente

  • return valoare; într-un generator: nu este permis. Folosește yield; un return; fără valoare doar oprește generatorul.
  • Să crezi că generatorul a rulat când l-ai apelat: fără parcurgere (for, toList), codul din el nu se execută deloc.
  • Parcurgerea de două ori a aceluiași Iterable generat: generatorul rulează din nou de la început (și efectele lui se repetă).

Comenzi înrudite

  • async*: O funcție async* produce un Stream: trimite valori cu yield, în timp, și poate folosi await între ele.
  • for-in: Parcurge pe rând elementele unei liste, unui set sau oricărui Iterable, fără indice.
  • await for: Parcurge un Stream element cu element, așteptând fiecare eveniment; se termină când stream-ul se închide.
  • funcții: Blocuri de cod cu nume, care primesc parametri și returnează o valoare; în Dart sunt și obiecte.