callback چیست؟
در جاوا اسکریپت، توابع شهروندان درجه یک هستند. شما می توانید یک تابع را به عنوان آرگومان به تابع دیگری ارسال کنید. callback تابعی است که شما آن را به تابع دیگری به عنوان آرگومان برای اجرای بعد ارسال می کنید.
به عبارت دیگر callback تابعی است که باید پس از پایان یافتن تابعی دیگر اجرا شود. از این رو به صورت «call back» یعنی «بازگشت فراخوانی» نامگذاری شده است.
همانطور که می دانیم تابع ها در JavaScript شیء هستند. به همین دلیل تابع ها می توانند تابع های دیگری را به عنوان آرگومان بپذیرند و می توانند از سوی تابع های دیگر بازگشت یابند. تابع هایی که این کار را انجام می دهند تابع های درجه بالا (higher-order functions) نامیده می شوند. هر تابعی نیز که به صورت یک آرگومان ارسال می شود تابع callback نامیده می شود.
callback ها از اهمیت بسیار زیادی برخوردار هستند زیرا که جاوا اسکریپت یک زبان برنامه نویسی رویداد-محور (event driven) است. بنابراین با کمک callback ها به جای این که برای ادامه برنامه منتظر بازگشت پاسخ از یک تابع باشیم، در جاوا اسکریپت همزمان با منتظر ماندن برای رویدادهای دیگر، به ادامه اجرای کدها می پردازیم. در ادامه مثال های کاربردی جهت آموزش نحوه کار و استفاده از callback ها به تفصیل نشان داده شده است
قطعه کد زیر تابع ()filter را تعریف می کند که آرایه ای از اعداد را می پذیرد و آرایه جدیدی از اعداد فرد را برمی گرداند:
این کد چگونه کار می کند.
1- ابتدا تابع ()filter را تعریف کنید که آرایه ای از اعداد را می پذیرد و آرایه جدیدی از اعداد فرد را برمی گرداند.
2- در مرحله دوم، آرایه numbers را تعریف کنید که دارای اعداد فرد و زوج باشد.
3- در مرحله سوم، تابع ()filter را فراخوانی کنید تا اعداد فرد را از آرایه اعداد خارج کنید و نتیجه را به دست آورید.
اگر می خواهید یک آرایه حاوی اعداد زوج را برگردانید، باید تابع ()filter را تغییر دهید. برای اینکه تابع ()filter عمومی تر و قابل استفاده مجدد باشد، می توانید:
1- ابتدا منطق موجود در بلوک if را استخراج کرده و آن را در یک تابع جداگانه قرار دهید.
2- سپس، تابع را به عنوان آرگومان به تابع ()filter منتقل کنید.
کد آپدیت شده به صورت زیر است:
نتیجه یکسان است. با این حال، می توانید هر تابعی را که یک آرگومان را می پذیرد و مقدار بولین را برمی گرداند به آرگومان دوم تابع ()filter ارسال کنید.
برای مثال، می توانید از تابع ()filter برای برگرداندن آرایه ای از اعداد زوج مانند زیر استفاده کنید:
طبق تعریف،isOdd و isEven توابع callback یا callbacks هستند. از آنجایی که تابع ()filter یک تابع را به عنوان آرگومان می پذیرد، به آن تابع مرتبه بالا (high-order function) می گویند.
یک callback می تواند یک تابع anonymous باشد، که تابعی بدون نام مانند زیر است:
در این مثال، به جای استفاده از یک تابع مجزا، یک تابع بی نام (anonymous function) را به تابع ()filter ارسال می کنیم.
در ES6، می توانید از یک arrow function مانند زیر استفاده کنید:
دو نوع callbacks وجود دارد: callback های همزمان و غیر همزمان (synchronous and asynchronous callbacks).
callback های همزمان (Synchronous callbacks)
یک synchronous callback در طول اجرای تابع مرتبه بالا که از callback استفاده می کند، اجرا می شود. isOdd و isEven نمونه هایی از callback های از نوع synchronous هستند زیرا در طول اجرای تابع ()filter اجرا می شوند.
callback های غیر همزمان (asynchronous callbacks)
پس از اجرای تابع مرتبه بالا که از callback استفاده می کند، یک callback از نوع asynchronous اجرا می شود.
غیر همزمانی (asynchronicity) به این معنی است که اگر جاوا اسکریپت باید منتظر باشد تا عملیاتی کامل شود، بقیه کد را در حین انتظار اجرا می کند.
توجه داشته باشید که جاوا اسکریپت یک زبان برنامه نویسی تک نخی (single-threaded) است که عملیات غیر همزمان را از طریق صف callback و event loop انجام می دهد.
فرض کنید که باید اسکریپتی ایجاد کنید که تصویری را از یک سرور راه دور دانلود کرده و پس از اتمام دانلود، آن را پردازش کند:
با این حال، دانلود یک عکس از یک سرور راه دور بسته به سرعت شبکه و اندازه تصویر زمان می برد.
تابع ()download زیر از تابع ()setTimeout برای شبیه سازی درخواست شبکه استفاده می کند:
و این کد تابع ()process را شبیه سازی می کند:
وقتی کد زیر را اجرا می کنید:
خروجی زیر را دریافت خواهید کرد:
این چیزی نیست که انتظار داشتید زیرا تابع ()process قبل از تابع ()download اجرا می شود. ترتیب درست باید به صورت زیر باشد:
1- عکس را دانلود کنید و منتظر بمانید تا دانلود کامل شود.
2- تصویر را پردازش کنید
برای حل این مشکل، می توانید تابع ()process را به تابع ()download ارسال کنید و پس از اتمام دانلود، تابع ()process را در داخل تابع ()download اجرا کنید، مانند این:
خروجی در ادامه نشان داده شده است.
در حال حاضر، این به همان شکلی که انتظار داشتیم کار می کند.
در این مثال، ()process یک callback است که به یک تابع asynchronous منتقل می شود.
هنگامی که از یک callback برای ادامه اجرای کد پس از یک عملیات asynchronous استفاده می کنید، این callback یک asynchronous callback نامیده می شود.
برای خلاصه تر کردن کد، می توانید تابع ()process را به عنوان یک تابع بی نام تعریف کنید:
کنترل خطاها
تابع ()download فرض می کند که همه چیز خوب کار می کند و هیچ استثنایی (exceptions) را در نظر نمی گیرد. کد زیر دو callbacks را معرفی می کند: succsess و failure به ترتیب برای رسیدگی به حالت های موفقیت و شکست:
callback های تودرتو و هرم Doom
چگونه سه عکس را دانلود کرده و آنها را به صورت متوالی پردازش می کنید؟ یک روش معمولی فراخوانی تابع ()download در داخل تابع callback است، مانند این:
خروجی در ادامه نشان داده شده است.
این اسکریپت کاملاً خوب کار می کند. با این حال، زمانی که پیچیدگی به طور قابل توجهی افزایش می یابد، این استراتژی callback مقیاس خوبی ندارد.
Nesting یا تودرتوی بسیاری از توابع غیرهمزمان در داخل کال بک ها به عنوان هرم عذاب (pyramid of doom) یا جهنم کال بک (callback hell) شناخته می شود.
برای اجتناب گرفتار شدن در هرم عذاب، از توابع promises یا async/wait استفاده کنید.
خلاصه آنچه که در این مقاله آموزشی JavaScript توضیح داده شد:
1- یک callback تابعی است که به عنوان آرگومان به تابع دیگری منتقل می شود تا بعدا اجرا شود.
2- تابع مرتبه بالا تابعی است که تابع دیگری را به عنوان آرگومان می پذیرد.
3- توابع callback می توانند همزمان یا غیرهمزمان باشند.