إعادة كتابة الشيفرة باستخدام بناء جملة Async Await - Perl

الأمور التي يجب مراعاتها عند إعادة كتابة الشيفرة لاستخدام بناء الجملة الجديد async/await
المقدم من Future::AsyncAwait
.
توفر فئة Future
وظيفة أساسية للمستقبلات غير المتزامنة، بالإضافة إلى عدد من اللبنات الأساسية المفيدة لبناء هياكل شيفرة أكبر إلى برامج ذات معنى. مع إضافة بناء الجملة async/await
الذي توفره Future::AsyncAwait
، لم يعد العديد من هذه الهياكل البرمجية ضرورية، لأن الشيفرة المعادلة يمكن كتابتها بأسلوب أقرب بكثير إلى شيفرة Perl التقليدية "المتسلسلة والمزامنة". بالإضافة إلى ذلك، توفر فوائد أخرى لاستخدام Future::AsyncAwait
والمستقبلات سببًا وجيهًا في كثير من الأحيان لإعادة كتابة الشيفرة لاستخدام هذه الأشكال الجديدة حيثما أمكن. في هذه السلسلة من المقالات، أهدف إلى تغطية العديد من الحالات التي قد تواجهها أثناء إجراء مثل هذه إعادة الكتابة، واقتراح كيف يمكن تغيير الشيفرة للاستفادة من البناء الجديد مع الحفاظ على السلوك الحالي.
في هذا الجزء الأول، سننظر في استخدامات Future لترتيب تنفيذ عدة أقسام شفرة مختلفة تتابع بعضها بعضًا.
->then
التسلسل
ربما يكون المثال الأكثر شيوعًا (ومن بين الأبسط) على الشيفرة المعتمدة على المستقبل هو استخدام دالة ->then
لجدولة مزيد من أقسام الشيفرة للتنفيذ بمجرد اكتمال القسم الأول. يمكن إعادة كتابة هذه باستخدام الكلمة المفتاحية await
للانتظار للتعبير الأول داخل async sub
، ثم كتابة المزيد من الشيفرة بعدها ببساطة. إذا كانت هناك سلاسل ->then
إضافية تلي ذلك، فيمكن استخدام المزيد من تعبيرات await
.
# سابقًا
sub example {
return FIRST()->then( sub {
كود في الوسط;
SECOND();
})->then( sub {
المزيد من الكود هنا;
THIRD();
});
}
# يصبح
async sub example {
await FIRST();
كود في الوسط;
await SECOND();
المزيد من الكود هنا;
await THIRD();
}
التسلسل Then في الشيفرة غير المتزامنة
من الجدير بالذكر أن تذكر استخدام await
حتى في التعبير النهائي للدالة، حيث يُتوقع أن تعيد الدالة THIRD()
مثيلًا من المستقبل. إذا لم يتم ذلك، فإن منادي دالة example
سيتلقى مستقبلًا تُكمل نتيجته بمجرد انتهاء SECOND()
، وستكون النتيجة نفسها مستقبلًا مزدوج التعشيش الذي أعادته THIRD()
. الأرجح أن المنادي لم يكن يرغب في تلك النتيجة.
بالإضافة إلى كونها أكثر ترتيبًا وقابلية للقراءة، وتحتوي على ضوضاء أقل من "آلية العمل" الناتجة عن استدعاءات طريقة ->then
، فإن حقيقة أن كل التعبيرات تظهر الآن ضمن نفس النطاق اللغوي لجسم الدالة يسمح لها باستخدام نفس المتغيرات اللغوية. هذا يعني أنك لم تعد بحاجة إلى "رفع" إعلانات المتغيرات خارج السلسلة إذا كنت بحاجة إلى التقاط قيم في جزء واستخدامها في جزء لاحق.
# سابقًا
sub example {
my $thing;
GET_THING()->then(sub {
($thing) = @_;
do_something_else();
})->then(sub {
USE_THING($thing);
});
}
# يصبح
async sub example {
my $thing = await GET_THING();
await do_something_else();
await USE_THING($thing);
}
شيفرة غير متزامنة لسلسلة ->then
->else
التسلسل
نظرًا لإمكانية استكمال مثيلات المستقبل إما نتيجة ناجحة أو فاشلة، فإن العديد من الأمثلة تستخدم طريقة ->else
لإرفاق شفرة لمعالجة الأخطاء. عند استخدام بناء الجملة async/await
، يؤدي فشل المستقبل إلى رمي استثناء من الكلمة المفتاحية await
، لذلك يمكن اعتراض ذلك باستخدام بناء الجملة try/catch
المقدم من Syntax::Keyword::Try
.
(من المهم استخدام Syntax::Keyword::Try
بدلاً من Try::Tiny
، إذ أن الأخير ينفذ بناء جملته باستخدام دوال داخلية مجهولة، مما يربك آلية الإيقاف والاستئناف في async/await
، بينما يستخدم الأول كتل شفرة مضمّنة كجزء من الدالة الحاوية، مما يسمح لـ async/await
بالعمل بشكل صحيح).
استخدام شائع لـ ->else
هو معالجة الفشل عن طريق إنتاج نوع من القيمة "الافتراضية" الأخرى، بدلاً من القيمة التي كانت الدالة الفاشلة سترجعها.
# سابقًا
sub example {
return TRY_A_THING()->else(sub {
my ($message) = @_;
warn "فشل تنفيذ الأمر - $message";
return Future->done($DEFAULT);
});
}
# يصبح
use Syntax::Keyword::Try;
async sub {
try {
return await TRY_A_THING();
} catch {
my $message = $@;
warn "فشل تنفيذ الأمر - $message";
return $DEFAULT;
}
}
مثال على شيفرة غير متزامنة
لاحظ هنا أن return
قادر على جعل الدالة الحاوية بأكملها تُرجع تلك النتيجة. أحيانًا، قد يكون هناك المزيد من الشيفرة بعد كتلة ->else
- ربما مرتبطة بواسطة ->then
آخر. في هذه الحالة، يجب علينا التقاط نتيجة try/catch
في متغير، لفحصها لاحقًا.
# سابقًا
sub example {
return TRY_GET_RESOURCE()->else(sub {
return Future->done($DEFAULT_RESOURCE);
})->then(sub {
my ($resource) = @_;
return USE_RESOURCE($resource);
});
}
# يصبح
use Syntax::Keyword::Try;
async sub example {
my $resource;
try {
$resource = await TRY_GET_RESOURCE();
} catch {
$resource = $DEFAULT_RESOURCE;
}
await USE_RESOURCE($resource);
}
مثال على شيفرة غير متزامنة
استخدام آخر لـ ->else
هو التقاط الفشل وإعادة رميه، مع تعليقه بطريقة ما لتوفير مزيد من التفاصيل. يمكننا التعامل مع ذلك عن طريق التقاط الاستثناء كما في السابق، وفحص التفاصيل منه، ثم إعادة رمي استثناء آخر. يمكننا استخدام Future::Exception->throw
لرمي استثناء يحتوي على التفاصيل الإضافية من المستقبل الفاشل:
# سابقًا
sub example {
my ($user) = @_;
return HTTP_GET("https://example.org/info/$user")->else(sub {
my ($message, $category, @details) = @_;
return Future->fail(
"غير قادر على الحصول على معلومات المستخدم لـ $user - $message",
$category, @details
);
});
}
# يصبح
use Syntax::Keyword::Try;
async sub example {
my ($user) = @_;
try {
return await HTTP_GET("https://example.org/info/$user");
} catch {
my $e = $@;
my $message = $e->message;
Future::Exception->throw(
"غير قادر على الحصول على معلومات المستخدم لـ $user - $message",
$e->category, $e->details
);
}
مثال على شفرة else في الشيفرة غير المتزامنة
->transform
توفر طريقة ->transform
وسيلة ملائمة لتحويل النتيجة إلى شكل مختلف، باستخدام كتلة شيفرة توفر دالة تحويل. نظرًا لأن عامل await
يمكن أن يظهر في أي مكان داخل التعبير، يمكن كتابة الشيفرة ذات الصلة بشكل أكثر مباشرة:
# سابقًا
sub example {
my ($uid) = @_;
return GET_USER_INFO($uid)->transform(
done => sub {
return { uid => $uid, info => $_[0] };
},
);
}
# يصبح
async sub example {
my ($uid) = @_;
return { uid => $uid, info => await GET_USER_INFO($uid) };
}
مثال على شفرة transform لـ async await
القيم المرجعية الفورية
أحيانًا لا تقوم الدالة بأية أعمال غير متزامنة فعليًا، بل تُرجع قيمة في مستقبل فوري باستخدام Future->done
، ربما لأسباب متعلقة بـ API أو الاتساق. في هذه الحالة، ولأن async sub
دائمًا ما تُرجع قيمتها عبر Future، يمكن ببساطة تحويل الدالة إلى async sub
وreturn
القيمة مباشرة - حيث سيتولى Future::AsyncAwait
باقي المعالجة:
# سابقًا
sub example {
return Future->done(\%GLOBAL_SETTINGS);
}
# يصبح
async sub example {
return \%GLOBAL_SETTINGS;
}
مثال على async await مع sub
في هذه الحالة، مجرد وجود async sub
لا يتطلب بالضرورة استدعاء await
في أي مكان داخلها. إذا لم نفعل، فإن الدالة ستعمل بشكل متزامن وتُنتج نتيجتها في مستقبل فوري.
كذلك، أحيانًا ترغب الدالة في إرجاع فشل فوري باستخدام Future->fail
. أي استثناء يُطرح داخل جسم async sub
يتحول إلى مستقبل فاشل، ولكن إذا أردنا تطبيق اسم الفئة والتفاصيل الإضافية، يجب علينا استخدام Future::Exception->throw
، كما رأينا سابقًا.
# سابقًا
sub example {
my ($req) = @_;
return Future->fail("معرّف المستخدم غير صالح", req => $req)
unless is_valid($req->{uid});
...
}
# يصبح
async sub example {
my ($req) = @_;
Future::Exception->throw("معرّف المستخدم غير صالح", req => $req)
unless is_valid($req->{uid});
...
}
مثال على فشل المستقبل في async await
في الجزء 2 سنستعرض أشكال التكرار المختلفة التي توفرها Future::Utils::repeat
، وفي الجزء 3 سنختتم بالنظر في الشروط والConcurrency المقدمة من needs_all
أو ما شابه.
ملاحظة: تم نقل هذه المقالة من https://tech.binary.com/ (مدونتنا التقنية القديمة). مؤلف هذه المقالة هو https://metacpan.org/author/PEVANS