Sparround

Future və async/await

Future<T> — gələcəkdə gələcək bir dəyəri (və ya xətanı) təmsil edən obyektdir. Üç vəziyyəti var: uncompletedcompleted with value və ya completed with error. Bir dəfə tamamlandıqdan sonra vəziyyət dəyişmir.

Əsas qaydalar:

  • async funksiya həmişə Future qaytarır. Future<int> f() async => 42; yazsan da, int yox, Future<int> alırsan. Heç nə qaytarmırsa Future<void>.
  • await future tamamlanana qədər həmin funksiyanı dayandırır, bütün proqramı yox — event loop işləməyə davam edir.
  • async funksiyanın içindən atılan istisna sinxron atılmır: o, qaytarılan future-u error ilə tamamlayır. Ona görə f() çağırışının özü try-a düşməz — await f() düşər.
  • Await edilməyən ("floating") future yenə də işləyir; sadəcə sən nəticəni və xətanı görmürsən. Bu, ən çox rast gəlinən async bug-dur.
KonstruksiyaNə edirNə vaxt istifadə et
`Future.value(x)`Dərhal dəyərlə tamamlanmış futureTest-də fake, cache hit, sinxron nəticəni async API-yə uyğunlaşdırmaq
`Future.error(e)`Xəta ilə tamamlanmış futureXəta yolunu test etmək
`Future.delayed(d, fn)``d` müddətindən sonra `fn`-i event queue-da işə salırRetry backoff, simulyasiya; **testdə `FakeAsync` ilə əvəzlə**
`Future.wait([...])`Hamısını paralel gözləyir, `List` qaytarırBir-birindən asılı olmayan çağırışlar
`Future.any([...])`İlk tamamlananın nəticəsiTimeout pattern-i, yarışan mənbələr
`future.timeout(d)``d` keçsə `TimeoutException` atırŞəbəkə çağırışlarına həmişə tətbiq et

Ardıcıl, yoxsa paralel? Bu, kod review-da ən çox rast gəlinən performans qüsurudur.

  • final a = await getUser(); final b = await getOrders(); — asılılıq yoxdursa, bu, vaxtları toplayır.
  • final [a, b] = await Future.wait([getUser(), getOrders()]); — hər ikisi eyni anda başlayır, ümumi vaxt ən yavaşı qədərdir.

Diqqət: Future.wait fail-fast-dır — biri error verən kimi ümumi future error ilə tamamlanır, amma qalanları dayanmır, arxa planda işləməyə davam edir. Onların da xətası varsa, o xətalar tutulmamış qalır. Hər nəticəni ayrıca görmək lazımdırsa eagerError yerinə hər future-a fərdi catchError qoy və ya nəticələri Result tipinə bük.

`then`/`catchError`, yoxsa `await`? Semantika eynidir, amma await üstündür: stack trace oxunaqlı olur, try/catch/finally işləyir, şərt və dövrələr təbii yazılır. then yalnız o halda qalır ki, funksiyanı async etmək istəmirsən (məsələn future-u sadəcə transformasiya edib qaytarırsan).

Async gap — mövzunun ən vacib anlayışı. await yazdığın hər yerdə funksiyan dayanır və nəzarət event loop-a qayıdır. Həmin "boşluq"da başqa hər şey baş verə bilər: istifadəçi ekranı bağlayır, obyekt dispose olunur, eyni funksiya ikinci dəfə çağırılır, cache təmizlənir.

Bundan iki praktik qayda çıxır:

  • `await`-dan sonra oxuduğun state-i yenidən yoxla. await-dan əvvəl doğru olan şərt sonra doğru olmaya bilər. Flutter-də bunun adı if (!mounted) return;-dir və analyzer-in use_build_context_synchronously xəbərdarlığı məhz bu problem haqqındadır.
  • Eyni əməliyyatın təkrar başlamasını idarə et. İki refresh() eyni anda işləyirsə, hansının nəticəsinin sonda yazılacağı zəmanətsizdir. Ya əvvəlkini ləğv et, ya da nəticəni bir "request id" ilə yoxla.

Async gap həm də timing-dən asılı bug-ların əsas mənbəyidir: onlar lokal maşında görünmür, çünki orada şəbəkə sürətlidir və boşluq qısadır.

Müsahibə ipucu. Üç sual demək olar zəmanətlidir.

"`async` funksiya nə qaytarır?" — cavab "həmişə Future", və dərhal nəticəni əlavə et: buna görə async funksiyanın içindən atılan istisna sinxron atılmır, future-u error ilə tamamlayır.

"Bu iki `await`-i necə sürətləndirərdin?"Future.wait de, sonra mütləq şərti əlavə et: yalnız aralarında asılılıq yoxdursa. Bu şərti deməyən namizəd yarımçıq cavab vermiş olur.

"Async gap nədir?"await-dan sonra dünyanın dəyişmiş ola biləcəyini izah et və konkret nümunə ver: await-dan sonra _disposed (Flutter-də mounted) yoxlanışı. Ən çox edilən səhv: await-in "proqramı dayandırdığını" demək. Dayanan yalnız həmin funksiyadır — event loop işləməyə davam edir.

📚 Mənbələr və sənədlər