طراحی سایت و برنامه نویسی

آموزش طراحی سایت و برنامه نویسی

طراحی سایت و برنامه نویسی

آموزش طراحی سایت و برنامه نویسی

برنامه تجزیه عدد به عوامل اول آن — به زبان ساده

در این مطلب، روش نوشتن برنامه تجزیه عدد به عوامل اول آن مورد بررسی قرار گرفته است. فرض می‌شود که عدد n داده شده است. هدف، نوشتن برنامه‌ای است که همه عوامل اول عدد n را پیدا کند. برای مثال، اگر عدد ورودی 12 است، خروجی باید «3 2 2» باشد و اگر ورودی 31۵ است، خروجی باید «۷ ۵ 3 3» باشد. در ادامه، گام‌های لازم برای پیدا کردن کلیه عامل‌های اول یک عدد ارائه شده است.

  1. اگر n بر 2 تقسیم‌پذیر است، عدد 2 را چاپ و سپس، n را بر 2 تقسیم کن.
  2. پس از گام 1، (اگر n بر 2 بخش‌پذیر نبود) n عددی فرد است. اکنون باید حلقه‌ای از  i = 3 تا ریشه دوم n زده شود. تا هنگامی که n بر i بخش‌پذیر است، i را چاپ و n را بر i تقسیم کن. در هر گام، i را دو واحد افزایش و کار را ادامه بده (زیرا عدد فرد است و صرفا قرار است بر مقسوم‌علیه‌های فرد خود تقسیم شود).
  3. اگر n عدد اول و بزرگ‌تر از 2 باشد، با گام‌های بالا، یک نخواهد شد. بنابراین، اگر n بزرگ‌تر از 2 است، آن را چاپ کن.

برنامه تجزیه عدد به عوامل اول آن در ++C

برنامه تجزیه عدد به عوامل اول آن در C

برنامه تجزیه عدد به عوامل اول آن در جاوا

برنامه تجزیه عدد به عوامل اول آن در پایتون

برنامه تجزیه عدد به عوامل اول آن در #C

برنامه تجزیه عدد به عوامل اول آن در PHP

خروجی حاصل از برنامه تجزیه عدد به عوامل اول

در ادامه، خروجی‌های قطعه کد بالا برای n = 31۵ محاسبه شده است.

3 3 5 7

روش کار قطعه کدهای بالا

در گام‌های 1 و 2 به اعداد مرکب و در گام 3 به اعداد اول پرداخته می‌شود. برای اثبات اینکه الگوریتم کامل کار می‌کند، نیاز به اثبات این است که گام‌های 1 و 2 به اعداد مرکب می‌پردازند. واضح است که در گام 1، اعداد زوج مورد بررسی قرار می‌گیرند. پس از گام اول، همه فاکتورهای اول باقیمانده باید فرد باشند (تفاوت دو عامل اول حداقل باید دو باشد)، به همین دلیل است که i در هر گام، دو واحد افزایش پیدا می‌کند. اکنون، بخش اصلی مربوط به حلقه‌ای است که تا ریشه دوم n ادامه پیدا می‌کند. برای اثبات اینکه این روش به درستی عمل می‌کند، خصوصیت زیر از اعداد مرکب در نظر گرفته می‌شود.

هر عدد مرکب، حداقل یک عامل اول کوچک‌تر یا مساوی ریشه دوم خود دارد.

این خصوصیت را می‌توان با استفاده از عبارت نقیض اثبات کرد. فرض می‌شود a و b دو عامل n هستند، به طوریکه a*b = n. اگر هر دو این موارد بزرگ‌تر از n√ باشند، a.b > √n و n√*  که با عبارت a * b = n تناقض دارد. در گام 2 از الگوریتم، حلقه اجرا و اقدامات زیر در آن انجام می‌شود:

  • کوچک‌ترین فاکتور اول i را پیدا کن (باید کمتر از n√ باشد)
  • همه وقوع‌های i در n را با تقسیم مکرر n بر i، پیدا کن
  • گام‌های a و b را برای n و i = i + 2 پیدا کن. گام‌های a و b تا هنگامی تکرار می‌شوند که n برابر با 1 یا یک عدد اول شود.


منبع: فرادرس

مدیریت پکیج در لاراول — به زبان ساده

در این مقاله به بررسی قابلیت مدیریت پکیج در لاراول می‌پردازیم. در طی این مطلب، به بررسی یک مثل عملی نیز خواهیم پرداخت تا مقصود مقاله را به طور کامل نمایش دهیم.

مدیریت پکیج در لاراول قابلیت مهمی است که امکان بسته‌بندی یک کارکرد را به طوری فراهم می‌سازد که بتوان آن را به سهولت توزیع کرد. به علاوه می‌توانید همواره پکیج خود را در ریپازیتوری‌هایی مانند Packagist و GitHub انتشار دهید. این ریپازیتوری‌ها به توسعه‌دهندگان امکان استفاده از پکیج‌ها را می‌دهند.

برای نمایش این مفهوم یک صفحه نمونه در لاراول می‌سازیم تا تصاویر را به کلود Amazon S3 آپلود کنیم. به این منظور به جای پیگیری گردش کار معمول آن را به صورت یک پکیج بسته‌بندی می‌کنیم که می‌تواند به سادگی توزیع یافته و نگهداری شود.

پیش از ادامه کار فرض ما بر این است که شما از قبل با فریمورک لاراول آشنا هستید، چون در این نوشته قرار است به بررسی جزییات مبانی مفاهیم لاراول بپردازیم.

ضمناً باید یک حساب معتبر AWS داشته باشید و از رمز عبور این حساب برای دسترسی به API آمازون استفاده کنید و به این ترتیب بتوانید مراحل این مقاله را عملاً پیگیری کنید. بنابراین ابتدا مطمئن شوید که این موضوع را تنظیم کرده‌اید. زمانی که همه چیز آماده شد، می‌توانیم به مراحل توسعه عملی بپردازیم.

راه‌اندازی فایل‌های پکیج

در ادامه فهرست خلاصه‌ای از فایل‌هایی را که در طی این دوره آموزشی پیاده‌سازی خواهیم کرد مشاهده می‌کنید.

  • composer.json – این نگاشت کلاس را باید به پکیج خود در فایل composer.json در ریشه پکیج اضافه کنیم.
  • config/app.php – این یک فایل از قبل موجود است که از آن برای افزودن مدخل ارائه‌دهنده سرویس سفارشی خودمان استفاده می‌کنیم و به این ترتیب می‌توانیم انواع «نما» (View) و «مسیر» (Route) را با استفاده از آن فایل بارگذاری کنیم.
  • composer.json – این فایل composer.json مختص پکیج است و برای پکیجی که می‌خواهید با دیگران به اشتراک بگذارید استفاده می‌شود.
  • packages/envato/aws/src/Providers/AwsServiceProvider.php – این فایل ارائه‌دهنده سرویس معمولی لاراول است که کامپوننت‌های دیگر پکیج را بارگذاری خواهد کرد.
  • packages/envato/aws/src/routes/web.php – این فایل مسیرهای سفارشی پکیج ما را بارگذاری می‌کند.
  • packages/envato/aws/src/Controllers/AwsController.php – این فایل کنترلر است که منطق اپلیکیشن پکیج ما را مدیریت می‌کند.
  • packages/envato/aws/src/views/upload.blade.php – فایل view است که اقدام به مدیریت منطق رندر می‌کند.

اگر از خواندن فهرست فوق چیز زیادی دستگیرتان نشد، نگران نباشید، چون در ادامه همه این موارد را به تفصیل بررسی خواهیم کرد.

راه‌اندازی پیش‌نیازها

چنان که پیش‌تر اشاره کردیم، پکیج ما، کاربرد آپلود فایل به کلود S3 آمازون را پیاده‌سازی می‌کند. در این بخش به بررسی پیش‌نیازهای لازم برای اجرای موفق پکیج می‌پردازیم.

شما به عنوان یک توسعه‌دهنده لاراول باید با Flysystem که یک لایه انتزاع زیبا برای تعامل با فایل‌سیستم ارائه می‌کند، آشنا باشید. این پکیج درایورهای با کاربری آسان ارائه می‌کند که به وسیله آن‌ها می‌توانید به راحتی با فایل‌سیستمهای مختلف کار کنید و مهم نیست که فایل‌سیستم روی یک دیسک محلی است و یا سیستم کلود S3 آمازون است.

برای فعال‌سازی پشتیبانی از فایل‌سیستم کلود S3 آمازون در Flysystem، شما باید پکیج کامپوزر آداپتر متناظر را نصب کنید. به این منظور دستور کامپوزر زیر را از ریشه پروژه اجرا کنید تا پکیج flysystem-aws-s3-v3 نصب شود:

composer require league/flysystem-aws-s3-v3

به محض اجرای موفق این دستور، می‌توانید از Flysystem برای تعامل با فایل‌سیستم کلود S3 آمازون به همان روش که برای فایل‌سیستم لوکال استفاده می‌کردید، بهره بگیرید.

اکنون می‌توانید فایل config/filesystems.php را به سرعت pull کرده و تنظیمات ارائه شده برای فایل‌سیستم S3 آمازون را مشاهده کنید.

چنان که می‌بینید، پیکربندی از قبل برای S3 آمازون تنظیم شده است و صرفاً کافی است که متغیرهای ENV مناسب را در فایل env. تنظیم کنیم. پا را فراتر گذارده و متغیرهای زیر را در فایل env. اضافه کنید.

AWS_KEY={AWS_KEY_VALUE}
AWS_SECRET={AWS_SECRET_VALUE}
AWS_REGION={AWS_REGION_VALUE}
AWS_BUCKET={AWS_BUCKET_VALUE}
AWS_CDN_URL={AWS_CDN_URL_VALUE}

البته باید مقادیر placeholder را با مقادیر واقعی‌شان پر کنید. اکنون آماده هستید که از آداپتر Flysystem AWS S3 در اپلیکیشن لاراول خود استفاده کنید.

بررسی فایل‌های پکیج

برای ایجاد پکیج لاراول، نخستین چیزی که باید ایجاد کنید یک ساختار دایرکتوری است که از قراردادهای سیستم لاراول تبعیت کند. فرض ما بر این است که یک اپلیکیشن لاراول ابتدایی را از قبل اجرا کرده‌اید. در واقع اپلیکیشن پیش‌فرض blog نیز به این منظور مناسب است.

در ادامه دایرکتوری Packages را در ریشه اپلیکیشن ایجاد می‌کنیم. با توجه به این که قصد داریم پکیج را با دیگران به اشتراک بگذاریم، ساختار ترجیحی پکیج باید به صورت {vendor_name}/{package_name} باشد.

با پیروی از همین قرارداد، می‌توانیم یک دایرکتوری envato/aws زیر دایرکتوری packages ایجاد کنیم. چنان که احتمالاً حدس می‌زنید، envato نام ارائه‌ دهنده است و aws اشاره به خود نام پکیج دارد. در نهایت یک دایرکتوری packages/envato/aws/src ایجاد می‌کنیم که فایل‌های منبع پکیج را نگهداری می‌کند.

اکنون باید وجود پکیج جدید خود را به لاراول اطلاع دهیم. به این منظور فایل composer.json را در ریشه اپلیکیشن لاراول باز می‌کنیم و مدخل “Envato\\Aws\\”: “packages/envato/aws/src” را در بخش autoload به صورت زیر اضافه می‌کنیم:

چنان که می‌بینید، فضای نام Envato\Aws\ به دایرکتوری packages/envato/aws/src نگاشت شده است. اکنون باید دستور dump-autoload را اجرا کرده و نگاشت‌های کامپوزر را باز تولید کنیم.

composer dump-autoload

در این مرحله می‌توانیم با استفاده از فضای نام Envato\Aws\ در اپلیکیشن، فایل‌ها را از مکان صحیحشان انتخاب کنیم.

فایل کامپوزر پکیج

در این مرحله یک فایل composer.json خاص پکیج اضافه می‌کنیم که می‌توان به وسیله آن پکیج را روی ریپازیتوری packagist توزیع کنیم. به این منظور به دایرکتوری packages/envato/aws بروید و دستور زیر را برای تولید فایل composer.json برای پکیج خود اجرا کنید:

composer init

در این مرحله برخی سؤالات معمولی از شما پرسیده می‌شود که باید به آن‌ها پاسخ دهید تا یک فایل composer.json ایجاد کنید. در کمترین حالت این فایل باید چیزی مانند زیر باشد:

مسیر

در این پکیج یک صفحه نمونه می‌سازیم که وضعیت فایل آپلود شده را نمایش خواهد داد. بنابراین باید یک مسیر ایجاد کنیم که با آن صفحه متناظر باشد. در ادامه فایل مسیر را در آدرس packages/envato/aws/src/routes/web.php به صورت زیر ایجاد می‌کنیم:

این فایل به توضیح چندانی نیاز ندارد. گام بدیهی بعدی نیز این است که فایل کنترلر مربوطه را ایجاد کنیم.

کنترلر

در این زمان یک فایل کنترلر در مسیر packages/envato/aws/src/Controllers/AwsController.php با محتوای زیر ایجاد می‌کنیم:

در ادامه جزییات فایل فوق را به تفصیل بررسی می‌کنیم تا با کارکرد هر یک از خطوط کد آشنا شویم.

در آغاز یک فضای نام برای کنترلر به صورت فضای نام Envato\Aws\Controllers تنظیم می‌کنیم. به خاطر داشته باشید که باید در ریشه composer.json یک نگاشت از Envato\Aws به packages/envato/aws/src اضافه کنید تا بتواند فایل‌های پکیج را پیدا کند.

سپس متد upload را تعریف می‌کنیم که برای همگام‌سازی فایل‌های محلی با کلود S3 آمازون یک متد ضروری محسوب می‌شود. این نکته مهم را نیز به خاطر داشته باشید که آرگومان نخست متد upload نیازمند وابستگی \Illuminate\Contracts\Filesystem\Factory است. در طی اجرای این متد، قرارداد لاراول مناسب تزریق خواهد شد.

اکنون می‌توانیم از وهله‌ای از filesystem factory برای ایجاد وهله‌های دیسک بسته به نیاز استفاده کنیم. وهله دیسک در لاراول، درایوری است که امکان دسترسی آسان به فایل‌سیستم‌های زیرین مانند دیسک محلی، کلود S3 آمازون و موارد مشابه را فراهم می‌سازد.

برای سادگی موضوع، فایل تصویر استاتیک را که از قبل در فضای ذخیره‌سازی محلی پیش‌فرض لاراول موجود و مسیر آن به صورت storage/app/test.jpg است را انتقال می‌دهیم. به عنوان نخستین گام محتوای فایل منبع را انتخاب می‌کنیم.

زمانی که همه چیز مطابق انتظار راه‌اندازی شد، می‌توانید فایل‌ها را با استفاده از متد زیر با S3 آمازون همگام‌سازی کنید.

در صورتی که موردی به درستی کار نکرد، باید مطمئن شوید که متغیرهای محیطی AWS به درستی تنظیم شده‌اند.

و آخرین کاری که باید انجام داد این است که یک فایل view را فراخوانی کنید تا تصویر همگام‌سازی شده و پیام مناسبی را نمایش دهد.

البته ما هنوز این فایل view را ایجاد نکرده‌ایم و این همان کاری است که دقیقاً در بخش بعدی قرار است اجرا کنیم.

View

در این بخش یک فایل ویو در مسیر packages/envato/aws/src/views/upload.blade.php و با محتوای زیر می‌سازیم:

این یک فایل ویوی کاملاً استاندارد است که تصویر آپلود شده را به محض آپلود موفق نمایش می‌دهد همچنین در صورتی که آپلود عکس، موفق باشد، پیام خطای مناسبی نمایش خواهد داد.

ارائه‌دهنده سرویس

ما کار خود را با پکیج تقریباً به پایان برده‌ایم، چون همه فایل‌های لازم را با موفقیت ایجاد کرده‌ایم. گام بعدی این است که یک ارائه‌دهنده سرویس بسازیم به طوری که بتوانیم مسیرها و ویوهای پکیج خود را بسازیم. در ادامه یک فایل ارائه‌دهنده سرویس در مسیر packages/envato/aws/src/Providers/AwsServiceProvider.php و با محتوای زیر ایجاد می‌کنیم:

بدیهی است که می‌توانستیم فایل ارائه‌دهنده سرویس را با استفاده از دستور آرتیزان نیز بسازیم. اما این کار نیازمند گام‌های اضافی انتقال فایل از app/Providers به پکیج بود.

در هر حال در ادامه به بررسی فایل ارائه‌دهنده سرویس که ایجاد کردیم، می‌پردازیم. در ابتدا مسیرها و ویوهای مرتبط با پکیج را بارگذاری می‌کنیم.

سپس پشتیبانی از انتشار ویوهای پکیج را ارائه می‌کنیم به طوری که توسعه‌دهندگانی که می‌خواهند ویوها را override کنند بتوانند این کار را انجام دهند. دفعه بعد که دستور php artisan vendor:publish اجرا شود، لاراول ویوها را از packages/envato/aws/src/views/ به resources/views/vendor/aws کپی می‌کند.

اکنون توسعه‌دهندگان دیگر می‌توانند ویوها را زیر دایرکتوری resources/views/vendor/aws تغییر دهند و این فایل‌ها به جای ویوهای زیر دایرکتوری packages/envato/aws/src/views/ به صورت خودکار از سوی لاراول انتخاب می‌شوند. در واقع، این روش صحیح ایجاد تغییر در ویوهای پکیج شخص ثالث به جای تغییر دادن مستقیم ویوهای پکیج محسوب می‌شود.

بدین ترتیب کار ما با فایل ارائه‌دهنده سرویس به پایان می‌رسد. چنان که انتظار دارید باید مدخل ارائه‌دهنده سرویس را در config/app.php اضافه کنیم. مدخل زیر را در آرایه providers وارد کنید.

بدین ترتیب کار ما به پایان می‌رسد. اینک همه چیز نظم خود را یافته است و از این رو می‌توانیم در ادامه به تست پکیج خود بپردازیم.

در ادامه URL به صورت http://your-laravel-application/aws/s3/upload را در مرورگر خود وارد کنید. اگر همه چیز به درستی پیش برود باید تصویری را که از کلود S3 آمازون بارگذاری می‌شود ببینید. بدین ترتیب راهنمای ما نیز به پایان می‌رسد.

سخن پایانی

در این مقاله به بررسی یکی از مهم‌ترین قابلیت‌های فریمورک لاراول یعنی مدیریت پکیج پرداختیم. در فرایند راه‌اندازی پکیج سفارشی لاراول یک مثال عملی را بررسی کردیم که روش آپلود تصویر روی کلود S3 آمازون را نمایش می‌داد.

مدیریت پکیج قابلیت زیبایی در لاراول است که به وسیله آن می‌توان مجموعه‌ای از کارکردها را به صورت مجموع و کنار هم توزیع کرد. در واقع آن را می‌توان به عنوان یک گزینه برای رویکرد توسعه ماژول سفارشی در لاراول نگریست.


منبع: فرادرس

قابلیت های جدید پایتون 3.۸ — راهنمای کاربردی

چند قابلیت وجود دارند که به نسخه 3.8 پایتون اضافه شده‌اند. برخی از این قابلیت‌ها بسیار شگفت‌انگیز هستند. در این مقاله برخی از قابلیت‌های جدید پایتون 3.8 را بررسی می‌کنیم که باید با آن‌ها آشنا باشید.

انتساب عبارت‌ها با =:

این عملگر به نام عملگر walrus نیز شناخته می‌شود. در این عملگر مقدارها به عنوان بخشی از عبارت بدون مقداردهی اولیه قبلی آن‌ها به متغیر انتساب می‌یابند.

در این قطعه کد، متغیر my_variable به یک مقدار انتساب می‌یابد. این قابلیت در خلاصه لیست‌ها و دیگر ساختارهای عبارت وجود ندارد و تنها در فرم‌های گزاره در دسترس ما است.

پارامترهای صرفاً موقعیتی

اگر می‌خواهید در پایتون به یک تابع اعلام کنید که پارامترهایی را بپذیرد، در این صورت می‌توانید آرگومان‌ها را از طریق موقعیت یا به وسیله کلیدواژه ارسال کنید. اما اگر بخواهیم فراخوانی کننده‌های API خودمان را طوری محدود کنیم که تابع ما را صرفاً با ارسال پارامترها بر اساس موقعیت فراخوانی کنند چطور؟ کارکرد پارامتر صرفاً موقعیتی به همین منظور طراحی شده است:

در نتیجه (add(1,2,3 و (add(1,2,3,4 هر دو فراخوانی‌های معتبری هستند، با این حال (add(a=1,b=2,c=3 یا (add(1,2,3,d=4 هر دو فراخوانی‌های غیر معتبری هستند.

برای سهولت دیباگ، f-string اینک از = پشتیبانی می‌کند

اکنون توصیفگر = می‌تواند به f-string–ها اضافه شود. f-string- ها به شکل {‘f'{expr= هستند، به ‘=’ دقت کنید. علامت تساوی می‌تواند به ارزیابی عبارت کمک کند.

کد فوق خروجی زیر را نمایش می‌دهد:

100-50=50

بدین ترتیب دیباگ کردن زمانی که تابع ارزیابی و خروجی پردازش می‌شود کار بسیار آسانی خواهد بود.

functools.lru_cache می‌تواند به عنوان یک دکوراتور اضافه شود

functools.lru_cache برای فراخوانی‌های بازگشتی بسیار عالی است. از یک دیکشنری برای کش کردن نتایج استفاده می‌شود. می‌توانیم دکوراتور functools.lru_cache را اضافه کنیم:

پایتون 3.8

توالی معکوس


برخی اوقات لازم است که یک تکرارکننده را معکوس کنیم. خوشبختانه ورودی‌های معکوس اینک به شیءهای Dict و dictviews اضافه شده‌اند. آرگومان ورودی در تابع معکوس باید یک تابع ()__reversed__ داشته باشد یا باید متد ()__len__ و __()getitem__ را داشته باشد که آرگومان‌هایشان از 0 آغاز می‌شوند.

منبع: فرادرس

تابع های تعریف شده توسط کاربر در ++C — راهنمای کاربردی

در این بخش از سری مقالات آموزش زبان برنامه‌نویسی ++C، در مورد رویکردهای مختلفی که می‌توانید برای حل یک مسئله به کمک تابع‌ها به کار گیرید، بحث خواهیم کرد. بدین ترتیب با مفهوم تابع های تعریف شده توسط کاربر آشنا می‌شویم. برای مطالعه بخش قبلی این سری مقالات به لینک زیر مراجعه کنید:

برای اینکه درک بهتری از آرگومان‌ها و مقادیر بازگشتی در تابع‌ها داشته باشید، باید اشاره کنیم که تابع‌های تعریف شده توسط کاربر را می‌توان به صورت زیر دسته‌بندی کرد:

  • تابع بدون آرگومان و بدون مقدار بازگشتی
  • تابع بدون آرگومان ولی با مقدار بازگشتی
  • تابع با آرگومان ولی بدون مقدار بازگشتی
  • تابع با آرگومان و با مقدار بازگشتی

موقعیتی را تصور کنید که باید اول بودن عددی را بررسی کنید. این مسئله در ادامه با تعریف توابع توسط کاربر به 4 روشی که در بالا اشاره شد، حل شده است.

مثال 1

هیچ آرگومانی ارسال نمی‌شود و مقداری نیز بازگشت نمی‌یابد:

در برنامه فوق، ()prime از ()main و بدون هیچ آرگومانی فراخوانی می‌شود. ()Prime عدد صحیحی را از کاربر می‌گیرد و بررسی می‌کند که آیا عدد اول است یا خیر. از آنجا که نوع مقدار بازگشتی از ()prime به صورت void است، هیچ مقداری از تابع بازگشت نخواهد یافت.

مثال 2

هیچ آرگومانی ارسال نمی‌شود، اما مقداری بازگشت می‌یابد:

در برنامه فوق، تابع ()primes از ()main فراخوانی می‌شود اما آرگومانی ندارد. ()Prime عدد صحیح مثبتی از کاربر می‌گیرد. از آنجا که نوع مقدار بازگشتی یک int است، عدد وارد شده از کاربر را به تابع فراخوانی کننده ()main بازگشت می‌دهد. سپس این که عدد مربوطه اول است یا نه را در خود ()main بررسی می‌کند و روی صفحه، نمایش می‌دهد.

مثال 3

آرگومان‌هایی ارسال می‌شوند، اما مقداری بازگشت نمی‌یابد:

در برنامه فوق، عدد صحیح در ابتدای امر از کاربر پرسیده و در متغیر num ذخیره می‌شود. سپس num به تابع ()prime ارسال می‌شود که در آنجا اول بودن آن بررسی شده و پرینت می‌شود. از آنجا که نوع بازگشتی ()prime به صورت void است، هیچ مقداری از تابع بازگشت نمی‌یابد.

مثال 4

آرگومان‌هایی ارسال می‌شوند و مقداری بازگشت می‌یابد:

در برنامه فوق یک عدد صحیح از کاربر پرسید می‌شود و در متغیر num ذخیره می‌شود. سپس num به تابع ()prime ارسال می‌شود که در آنجا اول بودن آن بررسی می‌شود. از آنجا که نوع بازگشتی ()prime به صورت int است، بسته به اول بودن یا نبودن عدد ورودی مقدار 1 یا 0 به تابع فراخوانی‌کننده ()main بازگشت می‌یابد. بدین ترتیب اگر عدد مربوطه اول باشد، مقدار بازگشتی 1 و در غیر این صورت صفر خواهد بود. اگر به تابع ()main بازگردیم، مقدار بازگشتی 1 یا 0 در متغیر flag ذخیره می‌شود و متن متناظر روی صفحه نمایش می‌یابد.

کدام روش بهتر است؟

هر چهار برنامه فوق خروجی یکسانی عرضه می‌کنند و همگی آن‌ها از نظر فنی صحیح هستند. هیچ قاعده سریع یا قاطعی در مورد این که کدام روش باید انتخاب شود وجود ندارد. روش خاصی که انتخاب می‌شود به موقعیت مربوطه و شیوه حل مسئله وابسته است.


    منبع: فرادرس

    برنامه نویسی ناهمگام جاوا اسکریپت با Promise ها — راهنمای کاربردی

    Promise-ها قابلیت نسبتاً جدیدی در زبان جاوا اسکریپت هستند که امکان به تأخیر انداختن برخی گزینه‌ها تا پایان یافتن برخی کارها را می‌دهند. همچنین با استفاده از Promise می‌توان در صورت ناموفق بودن یک کار به آن پاسخ داد. این وضعیت برای راه‌اندازی یک توالی از عملیات ناهمگام که به صورت صحیحی کار می‌کنند کاملاً مفید است. در این مقاله به بررسی روش برنامه نویسی ناهمگام جاوا اسکریپت و طرز کار Promise می‌پردازیم، شیوه استفاده از آن‌ها در API-های وب را بررسی می‌کنیم و با شیوه نوشتن آن‌ها آشنا خواهیم شد.

    پیش‌نیازهای مطالعه این مقاله داشتن سواد مقدماتی کامپیوتر و درک معقولی از مبانی جاوا اسکریپت است. هدف از این مقاله نیز درک Promise-ها و روش استفاده از آن‌ها است. برای مطالعه قسمت قبلی این مجموعه مطلب آموزشی می‌توانید روی لینک زیر کلیک کنید:

    Promise-ها چه هستند؟

    ما قبلاً در برخی مطالب دیگر مجله فرادرس ساختار Promise را در جاوا اسکریپت مورد بررسی قرار دادیم. در این مقاله به بررسی دقیق این ساختار نسبتاً جدید می‌پردازیم.

    Promise اساساً شیئی است که یک حالت میانجی عملیات را نمایش می‌دهد. در واقع Promise نوعی نتیجه است که در نقطه‌ای از زمان در آینده رخ می‌دهد. تضمینی وجود ندارد که عملیات دقیقاً چه زمانی پایان می‌یابد و نتیجه بازگشت می‌یابد، اما این تضمین وجود دارد که وقتی نتیجه آماده شد یا Promise ناموفق باشد، کد ارائه شده اجرا می‌شود تا با نتیجه بازگشتی کاری انجام دهد یا در صورت عدم موفقیت وضعیت مربوطه را مدیریت کند.

    به طور کلی ما علاقه‌ای به مقدار زمانی که طول می‌کشد تا یک عملیات ناهمگام پایان یابد و نتیجه را بازگشت دهد، نداریم و بیشتر علاقه‌مندیم که بتوانیم به نتیجه بازگشتی، هر زمان که آماده شود پاسخ دهیم. البته این که Promise باعث می‌شود اجرای بقیه کدها مسدود نشود هم بسیار عالی است.

    یکی از رایج‌ترین کاربردهای Promise-ها در آن دسته از Api-های وب است که Promise بازگشت می‌دهند. یک اپلیکیشن تماس ویدئویی فرضی را در نظر بگیرید. این اپلیکیشن پنجره‌ای دارد که لیستی از دوستان کاربر را نمایش می‌دهد و کاربر در آن می‌تواند با کلیک کردن روی دکمه کنار هر کاربر شروع به تماس با وی بکند.

    دستگیره دکمه اقدام به فراخوانی ()getUserMedia می‌کند تا به دوربین و میکروفن کاربر دسترسی پیدا کند. از آنجا که ()getUserMedia باید مطمئن شود که کاربر مجوز دسترسی به این امکانات گوشی را دارد از کاربرمی خواهد که انتخاب کند می‌خواهد از کدام میکروفن یا دوربین استفاده کند، ممکن است تا زمانی که کاربر این میکروفن و دوربین را انتخاب نکرده است و یا حتی تا زمانی که آن‌ها عملیاتی نشده‌اند مسدود شود. به علاوه کاربر ممکن است بی‌درنگ به این درخواست‌های مجوز پاسخ ندهد و این وضعیت مدت زمان زیادی طول بکشد.

    از آنجا که فراخوانی ()getUserMedia از نخ اصلی مرورگر اجرا می‌شود، کل مرورگر مسدود می‌شود تا این که ()getUserMedia بازگشت یابد. بدیهی است که این گزینه قابل قبول نیست. در واقع بدون وجود Promise همه چیز در مرورگر تا زمانی که کاربر تصمیم بگیرد در مورد میکروفن و دوربین می‌خواهد چه بکند از کار می‌افتند. بنابراین به جای انتظار برای کاربر، همچنین انتظار برای فعال شدن دستگاه مربوطه و بازگشت دادن مستقیم MediaStream برای استریم ایجاد شده از منابع منتخب، MediaStream یک Promise بازگشت می‌دهد که در زمان عرضه شدن آن اجرایی می‌شود.

    بدین ترتیب کدی که اپلیکیشن تماس ویدئویی استفاده می‌کند، چیزی مانند زیر است:

    این تابع کار خود را با فراخوانی به تابع ()setStatusMessage برای به‌روزرسانی وضعیت نمایش یافته با عبارت «…Calling» آغاز می‌کند و بدین ترتیب نشان می‌دهد که تماس در حال برقراری است. سپس ()getUserMedia را فراخوانی می‌کند و تقاضای یک استریم می‌کند که هر دو تِرَک ویدئو و صوتی را در خود دارد. در ادامه زمانی که این تِرَک به دست آمد، یک عنصر ویدئویی تنظیم می‌کند تا استریم آمده از دوربین را در یک self view نمایش دهد، سپس هر کدام از ترک‌های استریم را گرفته و آن‌ها را به RTCPeerConnection از نوع WebRTC اضافه می‌کند تا اتصال به کاربر دیگر را نمایش دهد. در نهایت وضعیت نمایش یافته به صورت «Connected» به‌روزرسانی می‌شود.

    اگر ()getUserMedia موفق نباشد، بلوک کد catch اجرا می‌شود. در این بلوک از ()setStatusMessage برای به‌روزرسانی وضعیت نمایش یافته جهت نمایش بروز خطا استفاده می‌شود.

    نکته مهم این است که ()getUserMedia تقریباً به صورت بی‌درنگ بازگشت می‌یابد، هر چند استریم دوربین هنوز به دست نیامده باشد. حتی اگر تابع ()handleCallButton به کدی که آن را فراخوانی کرده است بازگشت یافته باشد، زمانی که ()getUserMedia کار خود را تمام کند، دستگیره ارائه شده را فراخوانی می‌کند. تا زمانی که اپلیکیشن فرض نمی‌کند استریم کردن آغاز یافته است، می‌تواند به اجرای خود ادامه دهد.

    مشکل Callback-ها

    برای توضیح کامل این که چرا Promise-ها چیز خوبی هستند، بهتر است ابتدا در مورد سبک کدنویسی قدیمی Callback صحبت کنیم و این که چرا مشکل‌زا هستند.

    • به عنوان قیاس یک پیتزا را تصور کنید. برای تهیه موفق یک پیتزا باید مراحل خاصی طی شوند که اجرای خارج از ترتیب آن‌ها شاید هیچ معنایی نداشته باشد. همچنین اگر صبر نکنیم یک مرحله تمام شود و مستقیماً به سراغ مرحله بعد برویم، کار به درستی پیش نخواهد رفت.
    • ابتدا نوع پیتزا را انتخاب می‌کنید، در صورتی که در این خصوص مردد باشید ممکن است تصمیم‌گیری کمی طول بکشد و حتی اگر نتوانید تصمیم خود را نهایی بکنید، ممکن است تصمیم بگیرید غذای پُرسی بخورید.
    • سپس سفارش خود را ارائه می‌کنید. آماده شدن پیتزا ممکن است کمی طول بکشد و در صورتی که رستوران مواد لازم برای پخت آن را نداشته باشد، ممکن است سفارشتان ناموفق باشد.
    • در ادامه در صورت آماده شدن، پیتزا را می‌خورید. این اتفاق نیز در صورتی که کیف پول خود را فراموش کرده باشید و نتوانید هزینه پیتزا را بپردازید ممکن است ناموفق باشد.

    در روش callbacks به سبک قدیم، یک بازنمایی شبه کد از کارکرد فوق می‌تواند به صورت زیر باشد:

    این کد شلوغ و خواندن آن دشوار است و معمولاً به نام «جهنم Callback» نامیده می‌شود. این کد نیازمند آن است که ()failureCallback چندین بار فراخوانی شود و هر کدام مشکلات خود را دارند.

    بهبودهای Promise

    Promise-ها در موقعیت‌هایی مانند آن چه در بخش قبلی شرح دادیم، باعث می‌شوند که نوشتن، تجزیه و اجرای کد بسیار آسان‌تر شود. اگر شبه کد فوق را به جای Callback با استفاده از Promise-ها نمایش دهیم، چیزی مانند زیر خواهد بود:

    این کد بسیار بهتر است، دیدن آن چه که اتفاق می‌افتد آسان‌تر است. ما در این حالت تنها به یک بلوک ()catch. نیاز داریم تا همه خطاها را در آن مدیریت کنیم و موجب مسدود شدن نخ اصلی نمی‌شود. بدین ترتیب می‌توانیم در زمانی که منتظر آماده شدن پیتزا هستیم، به بازی کردن گیم بپردازیم. در این روش تضمین می‌شود که هر عملیات قبل از شروع منتظر می‌ماند تا عملیات قبلی پایان گیرد. ما می‌توانیم چند اکشن ناهمگام را به هم زنجیر کنیم تا یکی پس از دیگر به این روش اجرا شوند و هر بلوک ()then. یک Promise بازگشت دهد که وقتی بلوک ()then. بازگشت می‌یابد اجرا شوند. با استفاده از تابع‌های Arrow می‌توان این کد را باز هم ساده‌تر ساخت:

    یا حتی از این هم ساده‌تر نوشت:

    دلیل این که کد فوق کار می‌کند این است که تابع‌های Arrow به صورت () => x یک اختصار معتبر برای () => { ;return x } هستند.

    در نهایت شگفتی حتی می‌توان کدی به صورت زیر نوشت، چون تابع‌ها آرگومان‌هایشان را مستقیماً ارسال می‌کنند. بنابراین دیگر نیازی به لایه اضافی تابع‌ها نیست:

    خواندن کد فوق به آن سادگی کدهای قبل نیست، و در صورتی که بلوک‌ها پیچیده‌تر از آن چه که در اینجا استفاده کردیم باشند، ممکن است قابل استفاده نباشد.

    نکته: با استفاده از ساختار async/await می‌توان بهبودهای بیشتری ایجاد کرد. این ساختار را در بخش بعدی این سری مقالات بیشتر بررسی می‌کنیم.

    Promise-ها در ابتدایی‌ترین حالت خود مشابه شنونده‌های رویداد هستند، اما چند تفاوت وجود دارد:

    • Promise تنها یک بار می‌تواند موفق شود یا شکست بخورد. Promise نمی‌تواند دو بار موفق شود یا شکست بخورد و نمی‌تواند زمانی که عملیات پایان یافت، از حالت موفقیت به شکست و یا برعکس سوئیچ کند.
    • اگر یک Promise موفق شود یا شکست بخورد و شما در ادامه یک Callback موفقیت یا شکست اضافه کنید، Callback صحیح فراخوانی می‌شود هر چند رویداد قبلاً اتفاق افتاده باشد.

    توضیح ساختار مقدماتی Promise با یک مثال واقعی

    درک Promise-ها حائز اهمیت است، زیرا اغلب API-های مدرن وب از آن‌ها برای کارکردهایی استفاده می‌کنند که وظایف نسبتاً طولانی‌مدتی را اجرا می‌کنند. برای استفاده از فناوری‌های وب مدرن باید از Promise-ها بهره گرفت. در ادامه این فصل نگاهی به شیوه نوشتن Promise-های سفارشی خواهیم داشت، اما فعلاً برخی نمونه‌های ساده را بررسی می‌کنیم که در API-های وب مشاهده می‌شوند.

    در مثال اول، از متد ()fetch استفاده می‌کنیم که برای واکشی تصویری از وب استفاده می‌شود، متد ()blob برای تبدیل بدنه خام پاسخ واکشی شده به شیء Blob کاربرد دارد و در ادامه این blob را درون یک عنصر <img> نمایش می‌دهیم. این فرایند کاملاً شبیه به نمونه‌ای است که در مثال ابتدای این سری از مقالات مشاهده کردیم، اما در اینجا به روشی نسبتاً متفاوت عمل می‌کنیم تا کد مبتنی بر Promise خودمان را بنویسیم.

    قبل از هر چیز کد قالب خالی HTML زیر را در روی یک دایرکتوری در سیستم با نام «index.html» ذخیره کنید:

    این تصویر را نیز دانلود کرده و در دایرکتوری مربوطه قرار دهید:

    برنامه نویسی ناهمگام جاوا اسکریپت

    یک عنصر <script> در انتهای <body> در کد HTML قرار دهید.

    درون عنصر <script> کد زیر را اضافه کنید:

    این کد متد ()fetch را فراخوانی کرده و URL مربوط به تصویر را از شبکه به صورت یک پارامتر واکشی می‌کند. آن را می‌توان به عنوان یک شیء گزینه به صورت پارامتر دوم اختیاری نیز دریافت کرد، اما فعلاً از روش ساده‌تر استفاده می‌کنیم. ما شیء Promise بازگشتی از ()fetch را درون یک متغیر به نام promise ذخیره می‌کنیم. چنان‌که پیش‌تر گفتیم، این شیء یک حالت میانی را نمایش می‌دهد که در ابتدا نه موفق و نه ناموفق است. در واقع نام رسمی این حالت «در انتظار» (pending) است.

    برای پاسخ‌دهی به تکمیل موفق عملیات در هر زمان (در این مورد زمانی که responses بازگشت یابد) متد ()then. شیء promise را فراخوانی می‌کنیم. Callback درون بلوک ()then. تنها زمانی اجرا می‌شود که فراخوانی promise با موفقیت به پایان برسد و شیء Response را بازگشت دهد. بر مبنای ادبیات Promise این اتفاق زمانی رخ می‌دهد که عملیات fulfilled شده باشد. بدین ترتیب شیء Response بازگشتی به صورت یک پارامتر ارسال می‌شود.

    نکته: روش کار یک بلوک ()then. مشابه زمانی است که یک شنونده رویداد را با استفاده از ()AddEventListener به یک شیء اضافه می‌کنید. این بلوک تا زمانی که رویدادی رخ نداده باشد کار نمی‌کند. قابل‌توجه‌ترین تفاوت این است که ()then. هر بار که استفاده شود تنها یک بار اجرا می‌شود، در حالی که شنونده رویداد می‌تواند چندین بار فراخوانی شود.

    ما متد ()blob را بی‌درنگ روی پاسخ اجرا می‌کنیم تا مطمئن شویم که بدنه پاسخ به طور کامل دانلود شده است و زمانی که آماده باشد آن را به شیء Blob تبدیل می‌کنیم که می‌توان کاری روی آن انجام داد. نتیجه این وضعیت به صورت زیر بازگشت می‌یابد:

    که اختصاری برای کد زیر است:

    تا به اینجا توضیح کافی است. در ادامه کد زیر را در خط اول کد جاوا اسکریپت اضافه کنید:

    هر فراخوانی به ()then. ایجاد یک Promise جدید را تضمین می‌کند. این وضعیت بسیار دقیق است زیرا متد Blob نیز یک Promise بازگشت می‌دهد و می‌توانیم شیء Blob که بازگشت می‌دهد را با فراخوانی متد ()then. مربوط به Promise دوم به طور کامل اجرا کنیم. از آنجا که می‌خواهیم کار کمی پیچیده‌تری نسبت به اجرای یک متد منفرد روی blob اجرا کنیم و نتیجه را بازگشت دهیم باید بدنه تابع را این بار درون آکولاد قرار دهیم، چون در غیر این صورت با خطا مواجه خواهیم شد.

    کد زیر را به انتهای کد موجود بیفزایید:

    اکنون بدنه تابع اجراکننده را پر می‌کنیم. خطوط کد زیر را درون آکولادها اضافه کنید:

    ما در اینجا مشغول اجرای متد ()URL.createObjectURL هستیم و آن را در زمان تکمیل شدن اجرای Promise دوم به صورت یک پارامتر Blob بازگشتی ارسال می‌کنیم. بدین ترتیب یک URL بازگشت می‌یابد که به شیء اشاره می‌کند. در ادامه یک عنصر <img> ایجاد می‌کنیم و خصوصیت src آن را برابر با URL شیء قرار می‌دهیم و آن را به DOM الحاق می‌کنیم تا تصویر روی صفحه نمایش پیدا کند.

    اگر فایل HTML را که هم اینک ایجاد کردیم، ذخیره کنید و آن را در مرورگر بارگذاری نمایید، خواهید دید که تصویر مطابق انتظار در صفحه نمایش پیدا می‌کند.

    نکته: احتمالاً متوجه شده‌اید که این مثال‌ها تا حدودی ساختگی هستند. ما این کار را می‌توانستیم با یک عنصر <img> و تعیین خصوصیت src برابر با URL شیء رسانه‌ای نیز انجام دهیم و نیازی به این همه زنجیره ()fetch و ()blob نبود. با این حال این مثال را انتخاب کردیم، زیرا Promise-ها را به روش ساده‌ای معرفی می‌کند و دلیل آن مناسب بودن این رویکرد در کارکردهای واقعی نبوده است.

    پاسخ به شکست

    در بخش قبل یک مورد را فراموش کردیم اشاره کنیم. در کد فوق هیچ روشی برای مدیریت خطا در زمان شکست خوردن هر یک از promise-ها تعبیه نشده است. این شکست به زبان Promise «رد شدن» (Reject) نامیده می‌شود. در این حالت می‌توان رویه‌های مدیریت خطا را با اجرای متد ()catch. روی Promise قبلی اضافه کرد. کد زیر را اضافه کنید:

    برای این که عملکرد این کد را ببینید، یک URL نادرست برای تصویر وارد کنید و تصویر را مجدداً بارگذاری نمایید. خواهید دید که خطا در کنسول ابزارهای توسعه‌دهنده مرورگر نمایش پیدا می‌کند.

    البته اینک اتفاق بیشتری نسبت به زمانی که بلوک ()catch. کلاً وجود نداشت نمی‌افتد، اما اگر اندکی تأمل کنید متوجه می‌شوید که با افزودن این بلوک مدیریت خطا می‌توان آن را دقیقاً به روش دلخواه مدیریت کرد. در یک اپلیکیشن واقعی بلوک ()catch. می‌تواند اقدام به واکشی مجدد تصویر کند یا تصویر پیش‌فرض را نمایش دهد یا از کاربر بخواهد که URL تصویر دیگری را ارائه کند.

    زنجیره کردن بلوک‌ها به همدیگر

    روشی که تا به اینجا برای نوشتن کد استفاده کردیم یک روش کاملاً طولانی و دلیل این کار کمک به درک مطلب بوده است. چنان که قبلاً گفتیم می‌توان بلوک‌های ()catch. را به هم زنجیر کرد. بدین ترتیب کد فوق را می‌توان به صورت زیر نیز نوشت:

    به خاطر داشته باشید که مقدار بازگشتی از سوی یک Promise موفق به پارامتر ارسالی به بلوک ()then. بعدی تابع اجراکننده تبدیل می‌شود.

    نکته: بلوک‌های ()then()/.catch. در Promise-ها اساساً معادل ناهمگام بلوک try…catch در کد همگام هستند. به یاد بسپارید که try…catch همگام در کد ناهمگام عمل نمی‌کند.

    جمع‌بندی اصطلاحات Promise

    در بخش قبل در مورد مباحث زیادی صحبت کردیم، بنابراین تلاش می‌کنیم آن را در یک بخش کوتاه جمع‌بندی کنیم تا بتوانید در آینده در هنگام یادآوری این موضوع راحت‌تر مطالب را به خاطر بیاورید. همچنین می‌توانید چندین بار بخش فوق را مطالعه کنید تا به خوبی در ذهنتان جا بیفتد.

    • زمانی که یک Promise ایجاد می‌شود، نه در حالت موفقیت است و نه شکست، بلکه در حالت «انتظار» (Pending) است.
    • زمانی که Promise بازگشت می‌یابد گفته می‌شود که resolve شده است.
    • یک Promise موفق را Fulfilled می‌نامیم. این Promise یک مقدار بازگشت می‌دهد که می‌توان در بلوک ()then. در انتهای زنجیره Promise به آن دسترسی داشت. تابع اجراکننده درون بلوک ()then. مقدار بازگشتی را در اختیار دارد.
    • Promise ناموفق به نام rejected شناخته می‌شود. این Promise یک reason بازگشت می‌دهد که پیام خطایی است که نشان می‌دهد چرا Promise ناموفق بوده است. با مراجعه به بلوک ()catch. در انتهای زنجیره Promise می‌توان به این دلیل دسترسی یافت.

    اجرای کد در پاسخ به چند Promise موفق

    مثال فوق برخی از مبانی واقعی استفاده از Promise-ها را به ما نشان داد. اکنون به بررسی برخی قابلیت‌های پیشرفته‌تر می‌پردازیم. در آغاز باید گفت که زنجیره‌سازی پردازش‌ها برای رخ دادن یکی پس از دیگری ممکن است، اما اگر بخواهیم کد خاصی فقط پس از آن که همه Promise ها موفق بودند، اجرا شود چطور؟

    این کار با استفاده از متد استاتیکی به نام ()Promise.all میسر خواهد بود. این متد یک آرایه از Promise-ها به عنوان پارامتر ورودی می‌گیرد و یک شیء Promise را تنها در صورتی بازگشت می‌دهد که همه Promise-ها در آرایه موفق باشند. ساختار آن مانند زیر است:

    اگر همه Promise-ها موفق شوند، بلوک ()then. تابع اجراکننده یک آرایه‌ی شامل همه‌ی نتایج را به عنوان پارامتر می‌گیرد. اگر هر کدام از Promise-های ارسالی به شیء ()Promise.All رد شوند، کل بلوک رد خواهد شد.

    این متد می‌تواند کاملاً مفید باشد، تصور کنید که می‌خواهیم اطلاعاتی را به صورت دینامیک واکشی کنیم تا یک قابلیت UI را روی صفحه خود با محتوایی پر کنیم. در اغلب موارد بهتر است همه داده‌ها را دریافت کنیم و تنها محتوای کامل را نمایش دهیم تا این که بخواهیم اطلاعات ناقصی را به نمایش بگذاریم. در ادامه این مثال را عملاً می‌سازیم:

    کد زیر را روی سیستم خود در یک فایل به نام index.html قرار دهید:

    در این مورد نیز عنصر <script> را درست پیش از تگ پایانی <body/> قرار دهید.

    فایل‌های تصویر coffee.jpg ،tea.jpg و فایل متنی description.txt را دانلود کنید. همچنین می‌توانید فایل‌های خود را جایگزین کنید.

    ما در اسکریپت خود ابتدا بک تابع تعریف می‌کنیم که Promise-هایی را که قرار است به ()Promise.all ارسال شوند، بازگشت می‌دهد، بدین ترتیب اگر بخواهیم بلوک ()Promise.all را در پاسخ به پایان یافتن عملیات ()fetch اجرا کنیم ساده‌تر خواهد بود. روش کار به صورت زیر است:

    زمانی که Promise کامل شد، values ارسالی به دستگیره تکمیل، می‌تواند شامل سه شیء Response باشد که برای هر کدام به یک عملیات تکمیل شده تعلق دارد.

    با این حال ما نمی‌خواهیم این کار را انجام دهیم. برای کد ما مهم نیست که هر عملیات ()fetch چه زمانی انجام یافته است. بلکه می‌خواهیم داده‌ها را بارگذاری کنیم. این بدان معنی است که ما می‌خواهیم بلوک ()Promise.all را زمانی که داده‌ها در blob-های قابل استفاده بازگشت یافتند آن‌ها را به صورت تصاویر و متن نمایش دهیم. می‌توان تابعی نوشت که این کار را انجام دهد. کد زیر را درون عنصر <script> اضافه کنید:

    گرچه ممکن است کمی پیچیده به نظر برسد، اما آن را گام به گام بررسی می‌کنیم:

    قبل از هر چیز تابعی تعریف کنید و یک URL و یک رشته به آن ارسال کنید که نشان‌دهنده نوع منبعی باشد که قرار است واکشی شود.

    درون بدنه تابع، ساختار مشابهی داریم که در مثال نخست دیدیم. ما تابع ()fetch را برای واکشی منبعی در URL ذکر شده استفاده می‌کنیم و سپس آن را به Promise دیگری زنجیره‌سازی می‌کنیم که بدنه پاسخ دیکُد شده را بازگشت می‌دهد. این مقدار بازگشتی همواره متد ()blob در مثال قبلی است.

    با این حال، دو چیز در اینجا متفاوت هستند:

    قبل از هر چیز، Promise دوم که بازگشت می‌دهیم بسته به آن مقدار type که می‌خواهیم متفاوت است. درون تابع اجراکننده یک گزاره if … else if وجود دارد که بسته به نوع فایلی که قرار است دیکد شود، Promse متفاوتی بازگشت می‌دهد. در این مورد می‌توانیم بین blob و text انتخاب کنیم، اما می‌توان از انواع دیگری نیز استفاده کرد.

    تفاوت دوم این است که یک کلیدواژه return قبل از فراخوانی ()fetch اضافه کرده‌ایم. تأثیر آن این است که کل زنجیره اجرا می‌شود و سپس نتیجه نهایی، زمانی که مقدار بازگشتی تابع تعریف‌شده به دست آید، اجرا خواهد شد. در واقع، گزاره return نتیجه را به زنجیره فوقانی بازگشت می‌دهد.

    در انتهای بلوک یک فراخوانی ()catch. را نیز زنجیره کرده‌ایم که همه حالت‌های خطا را که ممکن است در هر Promise ارسالی به آرایه ()all. وجود داشته باشند، مدیریت می‌کند. اگر هر کدام از Promise-ها رد شوند، بلوک catch به شما اطلاع خواهد داد که کدام بلوک مشکل دارد. بلوک ()all. همچنان موفق است، اما منابعی را که مشکل دارند نمایش نمی‌دهد. اگر بخواهید all. رد شود باید بلوک ()catch. را به انتهای آن زنجیره کنید.

    کد درون بدنه تابع ناهمگام و مبتنی بر Promise است از این رو در عمل کل تابع به صورت یک Promise عمل می‌کند.

    در ادامه تابع خود را سه بار فراخوانی می‌کنیم تا شروع به واکشی و دیکد کردن تصاویر و متن بکند و هر کدام از Promise-های بازگشتی را در یک متغیر ذخیره کند. کد زیر را به انتهای کد قبلی اضافه کنید:

    سپس یک بلوک ()Promise.all تعریف می‌کنیم تا برخی کدها تنها زمانی که هر سه Promise ذخیره شده فوق با موفقیت اجرا شدند، به اجرا درآیند. در آغاز یک بلوک با اجراکننده خالی درون فراخوانی ()then. به صورت زیر اضافه کنید:

    چنان که می‌بینید این کد یک آرایه شامل Promise-ها به عنوان ورودی می‌گیرد. اجراکننده تنها زمانی اجرا خواهد شد که هر سه Promise بازگشت یابند. زمانی که این اتفاق بیفتد یک آرایه شامل نتایج از Promise-های منفرد بازگشت می‌یابد که تا حدودی شبیه به آرایه زیر است:

    [coffee-results، tea-results، description-results]

    در نهایت کد زیر را درون اجراکننده اضافه کنید. در این کد ما از کد همگام نسبتاً ساده‌ای برای ذخیره‌سازی متغیرها در متغیرهای جداگانه استفاده کردیم و سپس تصاویر و متن را روی صفحه نمایش می‌دهیم:

    صفحه را ذخیره و رفرش کنید تا کامپوننت‌های UI بارگذاری شده را مشاهده کنید، البته شاید ظاهر آن‌ها چندان جذاب نباشد. کدی که در اینجا برای نمایش آیتم‌ها ارائه شده است کاملاً ابتدایی است، اما فعلاً به عنوان مثال توضیحی کار می‌کند. اگر در هر مرحله از این کدنویسی مشکل داشتید می‌توانید از کد کامل شده زیر استفاده کنید:

    نکته: اگر می‌خواهید کد فوق را بهبود بدهید می‌توانید یک حلقه روی لیستی از آیتم‌هایی که قرار است نمایش یابند تعریف کنید و هر کدام را واکشی و دیکُد کنید. در ادامه روی نتایج درون ()Promise.all حلقه‌ای تعریف کنید و تابع متفاوتی را برای نمایش هر یک بسته به نوع کد مورد استفاده قرار دهید. بدین ترتیب می‌توانید کد فوق را برای هر تعداد از آیتم‌ها و هر نوع از آن‌ها استفاده کنید.

    علاوه بر آن می‌توانید نوع فایل واکشی شده را نیز بدون نیاز به وجود صریح مشخصه type تعیین کنید. برای نمونه می‌توانید هدر HTTP با عنوان Content-Type را در مورد هر پاسخ با استفاده از کد زیر بررسی کنید:

    بدین ترتیب می‌توانید بر اساس نوع هر فایل واکنش متناسبی داشته باشید.

    اجرای کد نهایی پس از موفقیت/شکست Promise

    مواردی وجود دارند که ممکن است بخواهید یک بلوک نهایی کد پس از تکمیل شدن Promise اجرا شود و مهم نیست که Promise موفق یا ناموفق بوده است. قبلاً دیدیم که می‌توان کد یکسانی را در Callback-های ()then. و ()catch. برای مثال به صورت زیر قرار دارد:

    در مرورگرهای جدیدتر متد ()finally. نیز وجود دارد که می‌توان به انتهای زنجیره‌ی Promise معمول زنجیره‌سازی کرد و امکان جلوگیری از تکرار کردن کد و اجرای منسجم‌تر کارها را فراهم می‌سازد. اکنون کد فوق می‌تواند به صورت زیر نوشته شود:

    برای مثال عملی نگاهی به کد زیر بیندازید:

    کد فوق دقیقاً همانند دموی ()Promise.all کار می‌کند که در بخش قبل دیدیم به جز این که در تابع ()fetchAndDecode یک متد ()finally زنجیره‌سازی کردیم که به انتهای آن اضافه می‌شود:

    بدین ترتیب پیام‌هایی در کنسول لاگ می‌شود که اعلام می‌کند چه زمانی fetch به پایان رسیده است.

    نکته: ()finally امکان نوشتن معادل‌های ناهمگام برای try/catch/finally را در کد همگام فراهم می‌سازد.

    ساخت Promise-های سفارشی

    خبر خوب این است که ما قبلاً به ترتیبی Promise سفارشی خود را ساخته‌ایم. زمانی که چندین Promise را با استفاده از بلوک‌های ()then. به هم زنجیر کنیم، یا این که آن‌ها را با ایجاد کارکرد سفارشی با هم ترکیب کنیم، در واقع تابع مبتنی بر Promise سفارشی ناهمگام خاص خود را ساخته‌ایم. برای نمونه تابع ()fetchAndDecode را در مثال قبلی در نظر بگیرید.

    ترکیب کردن API-های مبتنی بر Promise دیگر برای ایجاد کارکرد سفارشی با فاصله زیادی، رایج‌ترین روش برای انجام کارهای سفارشی با Promise-ها است و انعطاف‌پذیری و قدرت مدرن‌ترین API-ها را پیرامون همین مفهوم نمایش می‌دهد. با این حال روش دیگری نیز برای ساخت Promise سفارشی وجود دارد.

    استفاده از سازنده ()Promise

    شما می‌توانید Promise-های خاص خود را با استفاده از سازنده ()Promise بسازید. راه‌حل اصلی که این وضعیت به کارمی آید زمانی است که یک کد API مبتنی بر سبک کدهای ناهمگام قدیمی و غیر مبتنی بر Promise دارید و می‌خواهید آن را با Promise بازنویسی کنید. این وضعیت زمانی بسیار کارآمد است که لازم باشد از کد پروژه‌ها، کتابخانه‌ها یا فریمورک‌های قدیمی موجود به همراه کدهای مبتنی بر Promise جدید استفاده کنید.

    در ادامه مثال ساده‌ای را می‌بینید که در آن یک فراخوانی ()setTimeout درون یک Promise قرار گرفته است. بدین ترتیب دو تابع اجرا می‌شوند که Promise را با استفاده از عبارت «Success!» نهایی و یا در اصطلاح resolve می‌کنند.

    ()resolve و ()reject دو تابعی هستند که برای موفقیت یا شکست Promise اخیراً ایجاد شده مورد استفاده قرار می‌گیرند. در این حالت، Promise با عبارت «!Success» به صورت fulfilled درمی‌آید.

    بنابراین زمانی که این Promise را فراخوانی می‌کنید می‌توانید یک بلوک ()then. را به انتهای آن زنجیر کنید و بدین ترتیب یک رشته به صورت «!Success» ارسال می‌کند. در کد زیر یک پیام را به صورت هشدار ارائه می‌کنیم:

    یا این که صرفاً می‌توانیم بنویسیم:

    کد منبع کامل این مثال به صورت زیر است:

    مثال فوق چندان انعطاف‌پذیر نیست. Promise می‌تواند صرفاً با یک رشته fulfill شود و هیچ نوع شرایط ()reject نداشته باشد. بدیهی است که متد ()setTimeout هیچ شرایط شکستی ندارد و از این رو این مسئله در این مثال موضوعیت ندارد.

    رد کردن یک Promise سفارشی

    می‌توانیم یک Promise سفارشی بسازیم که درست مانند ()resolve با استفاده از متد ()reject ریجکت شود. این متد یک مقدار منفرد می‌گیرد، اما در این حالت این همان دلیل ریجکت شدن، یعنی خطایی است که به بلوک ()catch. ارسال خواهد شد.

    مثال قبلی را با نوعی شرایط ()reject بسط می‌دهیم و همچنین اجازه می‌دهیم پیام‌های مختلفی به محض موفقیت ارسال شوند.

    یک کپی از کد زیر روی سیستم خود بسازید:

    و تعریف ()timeoutPromise موجود را با کد زیر عوض کنید:

    در کد فوق دو آرگومان به تابع سفارشی خود ارسال می‌کنیم که یکی پیامی برای انجام یک کار و دیگری بازه زمانی است که باید پیش از انجام آن کار منتظر بماند. درون تابع یک شیء Promise جدید بازگشت می‌دهیم که تابعی را فرامی‌خواند که Promise مورد نظر ما را بازگشت می‌دهد.

    • درون سازنده Promise چند بررسی درون سازه if…else اجرا می‌کنیم.
    • قبل از هر چیز بررسی می‌کنیم که آیا پیام برای هشدار دادن مناسب است یا نه. اگر یک رشته خالی باشد یا اولاً رشته نباشد Promise را با پیام خطای مناسبی ریجکت می‌کنیم.
    • سپس بررسی می‌کنیم که آیا بازه مقدار مناسبی دارد یا نه. اگر منفی باشد یا عدد نباشد، Promise را با پیام خطای مناسبی ریجکت می‌کنیم.
    • در نهایت اگر پارامترها هر دو OK به نظر برسند، Promise پس از بازه معین شده با استفاده از ()setTimeout با پیام خاصی ریجکت می‌کند.
    • از آنجا که تابع ()timeoutPromise یک Promise بازگشت می‌دهد، می‌توانیم ()then() ،.catch. و غیره را با هم ترکیب کنیم. اکنون از آن استفاده می‌کنیم، کاربرد timeoutPromise قبلی را با کد زیر عوض می‌کنیم:

    زمانی که کد را ذخیره و اجرا کنید، پس از یک ثانیه، پیام هشدار را دریافت خواهید کرد. برای نمونه اکنون تلاش می‌کنیم پیام را به یک رشته خالی یا بازه را به شماره منفی تنظیم می‌کنیم و می‌توانید ببینید که Promise با پیام‌های خطای مناسبی ریجکت می‌شوند. همچنین می‌توانید چیز دیگری را نیز با پیام‌های resolve شده امتحان کنید,

    نکته: می‌توانید نسخه کامل این مثال را در ادامه مشاهده کنید:

    یک مثال واقعی

    مثال فوق عامدانه ساده حفظ شده تا درک مفاهیم آسان بماند، اما در عمل کاملاً ناهمگام نیست. ماهیت ناهمگام اساساً با استفاده از ()setTimeout جعل می‌شود، گرچه همچنان نشان می‌دهد که Promise-ها برای ایجاد تابع سفارشی با گردش کار معینی از عملیات، مدیریت مناسب خطا و موارد دیگر مفید هستند.

    یک مثال که شما را دعوت می‌کنیم تا مطالعه کنید کتابخانه idb مربوط به Jake Archibald است که اپلیکیشن ناهمگام مفیدی برای سازنده ()Promise نمایش می‌دهد. این کتابخانه از API مربوط به IndexedDB استفاده می‌کند که یک API مبتنی بر Callback به سبک قدیمی است که برای ذخیره‌سازی و بازیابی داده‌ها در سمت کلاینت استفاده می‌شود و امکان بهره‌گیری از آن به همراه Promise را می‌دهد. اگر به فایل کتابخانه اصلی نگاه کنید، می‌بینید که از همان نوع تکنیکی که در این نوشته معرفی کردیم استفاده شده است. در بلوک کد زیر یک مدل درخواست مقدماتی که از سوی تعداد زیادی از متدهای IndexedDB استفاده می‌شود برای بهره‌گیری از Promise تبدیل یافته است:

    این وضعیت به وسیله افزودن چند دستگیره رویداد عمل می‌کند که Promise را در زمان‌های مناسب fulfill یا Reject می‌کنند:

    • زمانی که رویداد موفقیت درخواست تحریک شود، دستگیره onsuccess اقدام به fulfill کردن Promise با نتیجه (result) درخواست می‌کند.
    • زمانی که رویداد خطای درخواست تحریک شود، اشیای دستگیره onerror اقدام به ریجکت کردن Promise با شیء error درخواست می‌کند.

    سخن پایانی

    Promise-ها روش مناسبی برای ساخت اپلیکیشن‌های ناهمگام هستند که وقتی مقدار بازگشتی از تابع یا میزان مدتی که بازگشت آن طول می‌کشد را ندانیم به کار می‌آیند. بدین ترتیب بیان و استدلال در مورد توالی عملیات ناهمگام بدون Callback-های عمیقاً تو در تو آسان‌تر می‌شود و از استایل مدیریت خطایی پشتیبانی می‌کنند که مشابه گزاره try…catch ناهمگام است.

    Promise-ها در جدیدترین نسخه‌ی همه مرورگرهای مدرن استفاده می‌شوند. تنها مکانی که پشتیبانی از Promise مشکل محسوب می‌شود، مرورگرهای Opera Mini و IE11 و نسخه‌های قبل‌تر آن است.

    ما در این مقاله همه قابلیت‌های Ptomise-ها را بررسی نکردیم، بلکه صرفاً انواع مفید و جالب‌تر را مورد بررسی قرار دادیم. زمانی که شروع به یادگیری Promise-ها بکنید، با قابلیت‌ها و تکنیک‌های بیشتری مواجه خواهید شد.

    اغلب API-های مدرن وب مبتنی بر Promise هستند، از این رو باید آن‌ها را به خوبی یاد بگیرید تا بتوانید بیشترین بهره‌برداری را از آن‌ها داشته باشید. از جمله این API-ها WebRTC ،Web Audio API ،Media Capture and Streams و موارد دیگر هستند. Promise-ها به مرور زمان اهمیت بیشتری کسب می‌کنند، بنابراین یادگیری استفاده از آن‌ها گام مهمی در یادگیری جاوا اسکریپت مدرن محسوب می‌شود.

    منبع: فرادرس