خیلی وقتها میخوای یه کپسول بسازی که دور یه کد ناهمزمان (asynchronous) بپیچه
تا بتونی از اون کد ناهمزمان توی کپسولهای دیگه استفاده کنی.
این یه مشکل جالب ایجاد میکنه، چون هر کپسولی بهصورت ذاتی همزمانی (synchronous) هست.
راهحل: اثرات جانبی!
اثرات جانبی در واقع مکانیزمی هستن که به رویدادهای خارجی اجازه میدن آپدیتهای کپسول رو فعال کنن،
و این دقیقاً چیزیه که اینجا نیاز داریم.
int countCapsule(CapsuleHandle _) => 0;
/// کپسول "خام" ناهمزمان ما که مستقیماً یه Future برمیگردونه (میتونه Stream هم برگردونه).
Future<int> delayedAsyncCapsule(CapsuleHandle use) async {
// همه خوندنهای کپسول و اثرات جانبی *باید* قبل از اولین `await` باشن.
// (ولی اغلب بهتره توی کپسولهای ناهمزمان از اثرات جانبی استفاده نکنی.)
final count = use(countCapsule);
final delayedCount = await Future.delayed(
const Duration(seconds: 1),
() => count,
);
return delayedCount + 1;
}
/// کپسول "wrapper" ما که یه AsyncValue برمیگردونه، و اغلب توی کد UI مفیدتره.
/// این wrapper همچنین مطمئن میشه که مقدار delayedAsyncCapsule توی کانتینر کش میشه،
/// چون این کپسول از یه اثر جانبی استفاده میکنه.
/// توجه کن که میتونی مستقیماً از `use.future` توی کد UI هم استفاده کنی،
/// اگه نمیخوای Future رو بهصورت مشتاقانه (eagerly) کش کنی.
AsyncValue<int> delayedCapsule(CapsuleHandle use) {
final delayed = use(delayedAsyncCapsule);
return use.future(delayed);
}// چند راه مختلف برای این کار وجود داره، ولی روش زیر استانداردترینه.
// TODO: مثال از rearch-tokio کپی بشه این بخش فقط برای Dart/Flutter هست، چون Futureها توی Rust یه مقدار متفاوت مدیریت میشن.
کپسول delayedCapsule که توی مثال بالا ساختیم، فقط یه دید "فقط-خوندن" از Future هست.
این کپسول هیچ حالت خودش رو نداره و فقط مقدار فعلی کپسول ناهمزمان رو ذخیره میکنه.
اگه بخوای حالت delayedCapsule رو تازهسازی (refresh) کنی، مثلاً با گرفتن داده جدید از آنلاین،
نیاز به یه رویکرد کمی متفاوت داریم که یه Future جدید رو جایگزین کنه.
اثر جانبی refreshableFuture به کمکمون میاد!
(AsyncValue<int>, void Function()) refreshableDelayedCapsule(CapsuleHandle use) {
return use.refreshableFuture(() {
return Future.delayed(const Duration(seconds: 1), () => 1234);
});
}یه معادل هم برای فقط باطل کردن (invalidation) وجود داره: invalidatableFuture.
فرقش اینه که invalidatableFuture فقط وقتی یه Future جدید اجرا میکنه که در حال حاضر استفاده بشه،
در حالی که refreshableFuture همیشه که تازهسازی بشه یه Future جدید اجرا میکنه.
ممکنه متوجه بشی که وقتی اثرات جانبی ناهمزمان رو اعمال میکنی،
دادههایی از نوع AsyncValue<T> یا AsyncState<T> بهت داده میشه.
این کاملاً عمدیه؛
این بهت اجازه میده حالت فعلی کد ناهمزمان رو بهصورت همزمانی مدیریت کنی،
که لازمه چون ساختهای ReArch کاملاً همزمانی هستن و (توی دارت) نمیتونن خطا پرتاب کنن.
برای همین، خیلی توصیه میشه که یاد بگیری چطور با AsyncValue/AsyncState کار کنی
بهجای اینکه سعی کنی ازشون دوری کنی.
AsyncValueها سه حالت رو برای Futureها و Streamها نشون میدن:
- Future/Stream هنوز در حال لود شدن و چیزی منتشر نکرده (
AsyncLoading) - Future/Stream داده منتشر کرده (
AsyncData) - Future/Stream یه خطا منتشر کرده (
AsyncError)
برای اطلاعات بیشتر، مستندات API مربوط به AsyncValue رو ببین.
AsyncStateها دو حالت رو برای Futureها نشون میدن:
- Future هنوز در حال لود شدن و چیزی منتشر نکرده (
Loading) - Future داده منتشر کرده (
Complete)
برای اطلاعات بیشتر، مستندات API مربوط به AsyncState رو ببین.
