async*, yield və transformasiyalar
Generator funksiyalar ardıcıllığı əl ilə yığmadan, tələb olunduqca yaradır. Dart-da iki növü var:
sync*→Iterable<T>qaytarır. Dəyərlər sinxron və tələb olunduqca (lazy) hesablanır:iterator.moveNext()çağırılmayınca gövdə işləmir.async*→Stream<T>qaytarır. Həryieldbir asinxrondatahadisəsidir; gövdədəawaitetmək olar.
Hər ikisində:
yield x— bir dəyər göndərir və funksiyanı həmin nöqtədə dayandırır.yield* other— başqa generator-a deleqasiya edir: onun bütün dəyərləri bu ardıcıllığa "düzlənir".for (final x in other) yield x;yazmaqla eynidir, sadəcə daha səmərəli və oxunaqlıdır.
async*-in mühüm xüsusiyyəti: dinləyici olmayanda işləmir. Subscription pause edilsə generator gövdəsi növbəti yield-də dayanır, cancel edilsə tamamilə dayanır. Bu, StreamController-dən əsas fərqdir — orada belə avtomatik backpressure yoxdur.
| Operator | Nə edir | Diqqət |
|---|---|---|
| `map(f)` | Hər hadisəni sinxron çevirir | `f` async-dırsa `Future` axını alırsan — `asyncMap` lazımdır |
| `where(test)` | Şərti ödəməyən hadisələri atır | Hadisə sayı azalır, `done` yenə gəlir |
| `take(n)` / `skip(n)` | İlk `n` hadisəni götürür / atır | `take` sayı dolanda **upstream-i ləğv edir** |
| `distinct()` | **Ardıcıl** təkrarları atır | Bütün tarixçəni yadda saxlamır — `a, b, a` üçün üçü də keçir |
| `asyncMap(f)` | Hər hadisə üçün `f`-i gözləyir, **sıranı saxlayır** | Paralel işləmir — yavaş `f` bütün axını ləngidir |
| `expand(f)` | Bir hadisəni bir neçəsinə açır | Sinxrondur; async lazımdırsa `async*` yaz |
| `transform(t)` | Tam nəzarətli `StreamTransformer` tətbiq edir | Debounce, throttle, buffer kimi zaman məntiqləri üçün |
Debounce və throttle — intuisiya. İkisi də "çox tez-tez gələn hadisələri azaltmaq" üçündür, amma məntiqləri fərqlidir:
- Debounce — axın susana qədər gözləyir, sonra sonuncu dəyəri buraxır. Axtarış sahəsi üçün budur: istifadəçi yazmağı dayandırandan 300 ms sonra bir sorğu.
- Throttle — müəyyən intervalda ən çox bir dəyər buraxır. Scroll və ya sensor üçün budur: saniyədə 60 hadisə əvəzinə 10.
Dart-ın öz kitabxanasında hazır debounce yoxdur; ya StreamTransformer.fromHandlers ilə Timer üzərində özün yazırsan, ya da rxdart-ın debounceTime/throttleTime operatorlarını götürürsən.
Backpressure — istehsalçı istehlakçıdan sürətli olanda nə baş verir? Dart-ın cavabı: single-subscription stream-də pause() mənbəni dayandırır (async* generator yield-də gözləyir, fayl oxunuşu dayanır) — bu, real backpressure-dır. Amma StreamController heç nə dayandırmır: add çağırışları buferə yığılır və yaddaş artır. Broadcast stream-də də mənbə dayanmır. Ona görə sürətli mənbə + yavaş istehlakçı ssenarisində ya async* generator (təbii backpressure), ya da açıq şəkildə sample/throttle/buffer məntiqi lazımdır.
*`async və StreamController` — hansını seçmək?** Praktik qayda:
- Stream-in dəyərləri kod tərəfindən istehsal olunursa (dövr, səhifələnmiş API-nin bütün səhifələri, retry zənciri) →
async*. Daha qısa, ləğvetməni özü idarə edir, backpressure verir vətry/finallyilə təmizləmə yazmaq mümkündür. - Hadisələr xaricdən gəlirsə (callback API, platform kanalı, manual event bus) →
StreamController. Burada sənin "nə vaxt" nəzarətin yoxdur, sadəcə gələniaddedirsən.
Bir vacib nüans: async* funksiyasında finally bloku subscription ləğv olunanda da işləyir — resursu bağlamağın etibarlı yolu budur:
Stream<String> readLines(File f) async* { final h = await f.open(); try { ... yield line; } finally { await h.close(); } }
StreamController-də isə eyni işi onCancel callback-i ilə əl ilə yazmalısan.
Müsahibə ipucu. İki sual tez-tez gəlir.
"`map` və `asyncMap` fərqi nədir?" — map sinxron çevirir; funksiyan async-dırsa nəticə Stream<Future<T>> olur və heç kim onları gözləmir. asyncMap isə hər nəticəni gözləyir. Kritik detalı əlavə et: `asyncMap` paralel işləmir və sıranı saxlayır — yavaş bir çağırış bütün axını ləngidir. Paralellik lazımdırsa hadisələri toplayıb Future.wait etmək və ya rxdart-ın flatMap variantlarını istifadə etmək lazımdır.
"`distinct()` təkrarları atır?" — tələ buradadır: yalnız ardıcıl təkrarları atır. a, b, a ardıcıllığında hər üçü keçir, çünki distinct tarixçə saxlamır. Bu bir cümlə ilə cavab verən namizəd dərhal seçilir.
Ən çox edilən səhv: async*-i StreamController-in sadəcə "qısa yazılışı" saymaq. Fərq backpressure-dadır — async* dinləyici gözləyəndə işini dayandırır, controller isə buferə yığmağa davam edir.
📚 Mənbələr və sənədlər
- Stream yaratmaqrəsmidart.dev