جشنواره فطر سون لرن

آموزش CallBack Functions در جاوا اسکریپت

دسته بندی: جاوا اسکریپت
زمان مطالعه: 11 دقیقه
۲۳ اردیبهشت ۱۳۹۹

در مقاله‌ی 7 نکته‌ی جالب درباره‌ی جاوا اسکریپت به بیان نکات جالب و در برخی موارد عجیب در زبان جاوا اسکریپت پرداختیم. شاید یکی از عجیب‌ترین مسائلی که بتوان درجاوا اسکریپت درباره‌ی آنها صحبت کرد، Function‌ها یا توابع باشند. توابع در زبان جاوا اسکریپت دنیایی از شگفتی هستند ومی توان با آنها کارهای جالبی انجام داد. برای مثال می‌توان یک تابع را درون یک متغیر ذخیره کرد، می‌توان توابع را به راحتی در توابع دیگر تعریف و یا آنها را تغییر داد. حتی توابع از پیش تعریف شده در جاوا اسکریپت را نیز می‌توان دست‌کاری کرد و تغییر داد! می‌توان یک تابع را به عنوان ورودی به تابع دیگر ارسال کرد و این‌گونه نسل پیشرفته‌ای از توابع را ساخت. در مقاله‌ی CallBack Functions در جاوا اسکریپت ، می‌خواهیم درباره‌ی این نسل پیشرفته از توابع، صحبت کنیم. اگر شما نیز مانند ما از تغییر دادن قوانین و به نوعی هنجارشکنی لذت می‌برید، این مقاله برای شما نوشته شده است، پس با ما همراه باشید.

فهرست محتوای این مقاله

آیا توابع در جاوا اسکریپت نوعی شی هستند؟

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

آیا می‌شود یک تابع را به عنوان آرگومان، به تابع دیگر ارسال کرد؟

پاسخ این سوال نیز مانند سوال قبلی مثبت است. توابع می‌توانند آرگومان‌های مختلفی را دریافت کنند. همچنین می‌توانند یک تابع را به عنوان آرگومان بپذیرند.

برای درک بهتر این موضوع، پیشنهاد می‌کنیم همراه ما قطعه کد زیر را در Editor وارد کنید:

    function firstFunc(Func){
	Func();
	}
    function secondFunc(){
	document.write( "welcome to 7learn");
	}
	firstFunc(secondFunc);

در قطعه کد بالا، تابعی با نام firstFunc تعریف کرده‌ایم و گفتیم این تابع به عنوان آرگومان یک تابع دریافت کند. در ادامه نیز تابعی با نام secondFunc تعریف کرده‌ایم. در خط بعدی تابع  firstFunc  را فراخوانی کرده و تابع secondFunc را به عنوان پارامتر به تابع ارسال کردیم.

در ادامه، تابع firstFunc،  تابع  secondFunc  را که به صورت پارامتر دریافت کرده بود، اجرا می‌کند. خروجی قطعه کد فوق به صورت زیر است:

welcome to 7learn

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

در چه مواقعی لازم است تابعی را به عنوان آرگومان به تابع دیگر ارسال کنیم؟

قبل از هر چیزی لازم است دو موضوع را بیان کنیم:

1- در دنیای کامپیوتر کدها به دو صورت پردازش می‌شوند:

- Asynchronous یا پردازش ناهمگام (غیر همزمان): در این نوع پردازش، زمانی که پردازشگر شروع به پردازش می‌کند، بدون اینکه منتظر اتمام پردازش اول باشد، پردازش دوم را شروع می‌کند.

- synchronous یا پردازش همگام (Blocking): در این نوع پردازش، پردازشگر ابتدا پردازش اول را به طور کامل به پایان می‌رساند، سپس پردازش دوم را شروع می‌کند.

2- جاوا اسکریپت یک زبان رویداد گرا است

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

برای فهم بهتر این مطلب به مثال زیر دقت کنید:

function Func1(){
document.write("I like video game very much"+'<br>');
}
function Func2(){
document.write("my favorite writer is Darren Hardy");
}
Func1();
Func2();

در قطعه کد بالا مفسر ابتدا تابع Func1 و سپس تابع Func2 را اجرا می‌کند. خروجی قطعه کد فوق به صورت زیر است:

I like video game very much

my favorite writer is Darren Hardy

حال می‌خواهیم با استفاده از متد setTimeout اجرای تابع اول را کمی زمان بر کنیم:

function Func1(){
 setTimeout(function (){
 document.write("I like video game very much"+'<br>'); 
 },1000);
 } 
 function Func2(){ 
 document.write("my favorite writer is Darren Hardy"); 
 } 
 Func1();
 Func2();

متد setTimeout  دو پارامتر ورودی دریافت می‌کند. پارامتر اول یک تابع است که اغلب به صورت Anonymous یا بدون نام تعریف می‌شود(مانند قطعه کد بالا).  پارامتر دوم یک عدد است که بیانگر این است که تابع بعد از چند میلی ثانیه تاخیر اجرا شود. برای اینکه بیشتر با این توابع آشنا شوید پیشنهاد می‌کنیم این مطلب را مطالعه کنید.

در نتیجه با استفاده از تابع setTimeout  در اجرای تابع Func1 تاخیر به وجود آوردیم. حال می‌خواهیم ببینیم در خروجی چه اتفاقی می‌افتد:

my favorite writer is Darren Hardy

//after 1000 ms:

I like video game very much

همان‌طور که در خروجی مشاهده می‌کنیم، از آنجایی که جاوا اسکریپت یک زبان رویدادگرا و مفسری است، اجرای تابع Func1 که به دلیل وجود تابع setTimeout  تاخیر داشت، در ابتدا اجرا نمی‌شود و مفسر به ترجمه‌ی دیگر کد‌ها ادامه می‌دهد. در نتیجه در خروجی ابتدا تابع Func2  اجرا می‌شود و پس از گذشت 1000ms تابع Func1 اجرا می‌شود.
 حال ما می‌خواهیم کاری کنیم که تابع دوم مادامی که تابع اول اجرا نشده است، اجرا نشود. کمی فکی کنید. چه راه حلی به ذهنتان می‌رسد؟ چه کار باید بکنیم؟
بیایید فرضیه ای را امتحان کنیم. طبق منطق، اگر یک متغیری را تعریف کنیم، مادامی که آن متغیر را استفاده نکرده باشیم اجرا نمی‌شود. برای مثال اگر متغیر رشته ای را تعریف کرده باشیم مادامی که آن متغیر را به تابعی مانند alert ارسال نکرده باشیم تا چاپ شود، چاپ نمی‌شود. می‌دانیم که می‌توانیم توابع را نیز به هم ارسال کنیم در نتیجه می‌توانیم تابعی را تعریف کنیم و به تابع دیگر ارسال کنیم. حال تا زمانی که تابع اول اجرا نشده باشد تابع دوم اجرا نمی‌شود. بیایید فرضیات خود را امتحان کنیم. در Editor قطعه کد زیر را می‌نویسیم:
function Func1(Func){
 setTimeout(function (){
 document.write("I like video game very much"+'<br>'); 
 Func();
 },5000);

 } 
 function Func2(){ 
 document.write("my favorite writer is Darren Hardy"); 
 } 
 Func1(Func2);

در نتیجه‌ی قطعه کد بالا، در خروجی داریم:

// after 5000 ms:

I like video game very much

I like video game

همان‌طور که مشاهده می‌کنیم، فرضیه‌ی ما درست بود و توانستیم کاری کنیم که مادامی که تابع اول اجرا نشده است، تابع دوم اجرا نشود. در نتیجه پس از 5000ms تابع اول اجرا می‌شود و سپس تابع دوم بلافاصله بعد از آن اجرا می‌شود.

توابع Callback در کجای این ماجرا هستند؟

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

Callback Function  در دنیای واقعی

تا اینجای مقاله‌ی CallBack Functions در جاوا اسکریپت ، مختصری با Callback Function و علت تعریف آنها آشنا شدیم، حال می‌خواهیم کمی تخصصی‌تر به بیان علت وجود این توابع و کاربرد آنها در اپلیکیشن‌ها اشاره‌ای کنیم.
در بیشتر زمانی که ما درحال نوشتن برنامه یا اپلیکیشنی هستم، می‌خواهیم که کدها به صورت همگام یا synchronous یا Blocking یا به عبارت دیگر به صورت مرتب اجرا شوند و هر چقدر که اجرای بخشی از کد زمانبر باشد، تا اجرا نشدن قطعه کد زمانبر، مفسر قطعه کد بعدی را اجرا نکند. فرض کنید در برنامه احتیاج به دریافت داده‌ای از یک سرور دیگر داریم و یا یک API تعریف کرده‌ایم که به اطلاعات دریافتی از آن، در ادامه‌ی برنامه نیاز داریم. حال اگر دریافت پاسخ از API یا سرور خارجی طولانی باشد، مفسر به سراغ کدهای دیگر رفته و آنها را اجرا می‌کند. در صورتی که اجرای صحیح کدهای بعدی، به دریافت اطلاعات از آن API یا سرور خارجی بستگی دارد.
برای حل این مشکل ما از CallBack Functions در جاوا اسکریپت ، استفاده می‌کنیم. و برنامه را مجبور می‌کنیم مادامی که پاسخ دریافت نشده کدهای بعدی اجرا نشود.
برای فهم بهتر این مسئله به قطعه کد زیر دقت کنید:
function serverRequest(query, callback){
  setTimeout(function(){
    var response = query + "full!";
    callback(response);
  },5000);
}v

function getResults(results){
  console.log("Response from the server: " + results);
}

serverRequest("The glass is half ", getResults);
 در قطعه کد بالا می‌خواهیم یک پاسخ را از یک سرور شبیه سازی شده دریافت کنیم.
می خواهیم برای فهم بهتر کد، کد را به سه بخش تقسیم کنیم، بیایید از آخر به اول قطعه کد فوق را بررسی کنیم:
بخش سوم: در این بخش یک تابع با نام serverRequest  داریم که دو پارامتر به عنوان ورودی دریافت می‌کند. یکی از این پارامتر‌ها به صورت رشته ای است و یکی دیگر به یک تابع اشاره می‌کند.
بخش دوم: در این بخش تابع getResult را با یک آرگومان ورودی داریم که با توجه به نام تابع قرار است یک نتیجه‌ای را برگرداند. در ادامه‌ی کد همان‌طور که می‌خوانیم، این تابع پاسخی را دریافت می‌کند و نتیجه را به برنامه اعلام می‌کند.
بخش اول: در بخش اول تابع serverRequest را داریم. این تابع دو آرگومان callback و query را به عنوان ورودی دریافت می‌کند. در اینجا callback تابعی است که قرار است به تابع اصلی serverRequest ارسال شود (که ما تابع getResult  را به آن ارسال کرده‌ایم) و query به صورت string نقش درخواست ارسالی به سمت سرور را بازی می‌کند. در ادامه برای اینکه زمانی که طول می‌کشد تا پاسخ از سرور دریافت شود را شبیه سازی کنیم از تابع setTimeout استفاده می‌کنیم و درون آن یک  Anonymous Function تعریف می‌کنیم. که این تابع شبیه سازی شده‌ی سروری است که query را دریافت می‌کند و response یا پاسخ درخواست را ارسال می‌کند. در ادامه پاسخ به عنوان نتیجه به تابع callback برای انجام یکسری عملیات ارسال می‌شود.
در خروجی قطعه کد فوق داریم:
// Result in console after 5 second delay:

Response from the server: The glass is half full!

با توجه به توضیحات بالا، به پاسخ این سوال فکر کنید. چرا از توابع callback استفاده کردیم؟
پاسخ این است چون اگر یک تابع  callback تعریف نمی‌کردیم، مفسر به اجرای کدها ادامه می‌داد و تابع getResult خروجی مطلوب را به ما نمایش نمی‌داد.

کمی پیشرفته تر

در ادامه‌ی مقاله‌ی CallBack Functions در جاوا اسکریپت ، می‌خواهیم مختصری درباره‌ی ارسال درخواست به API توییتر و دریافت پاسخ از این API صحبت کنیم:
T.get('search/tweets', params, function(err, data, response) {
  if(!err){
    // This is where the magic will happen
  } else {
    console.log(err);
  }
})

در قطعه کد بالا T.get را داریم که با استفاده از آن درخواستی را به توییتر ارسال می‌کنیم.

 T.get سه آرگومان را به عنوان ورودی دریافت می‌کند:

  1. search/tweets به عنوان مسیر درخواست
  2. params به عنوان موارد یا پارامترهای مورد جستجو
  3. و یک تابع که عملیات خاصی را انجام می‌دهد.

در ادامه، ما نمی‌دانیم که پاسخ توییتر به درخواست ما چه چیزی است. پس با یک شرط بررسی می‌کنیم که اگر خطایی دریافت نکردیم یکسری عملیات انجام شود (به نوعی پاسخ به تابع CallBack ارسال شود تا یکسری عملیات روی آن اجرا شود.)  و اگر خطایی دریافت کردیم در console خطا نمایش داده شود.

نتیجه گیری

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

 

چه امتیازی به این مقاله می دید؟
نویسنده فاطمه افشار
وقتی به برنامه نویسی فکر می کنم ، می رسم به این جمله -> " عشق چیز عجیبیه جدا ! "

نظرات کاربران

حسین سلیمی

سلام.عالی بود.

فاطمه افشار

سلام ممنونم از شما
باعث خرسندی هست که مفید بوده و تونستیم رضایت شما رو جلب کنیم

احسان جوانی

بسیار عالی ، خسته نباشید ….

فاطمه افشار

خیلی ممنونم از شما
خوشحالیم که مفید بوده

محسن محمدی رهنما

سلام . طاعات و عبادات تون قبول. مقاله تون عالی بود بود مثل همیشه فقط نکته ای هست دیگه کمتر از callback استفاده میشه چون وقتی بخوایم از callback ها در پروژه های نمیه سنگین و سنگین استفاده کنیم باعث بروز callback hell میشه . پس در این مواقع میایم از promise ها استفاده میکنیم که گزینه بهتری هست نسبت به callback ها . در کل بینهایت سپاسگذار بابت مقاله کاملا مفیدتون.

فاطمه افشار

سلام ممنونم از شما طاعات و عبادات شما هم قبول حق باشه
باعث خرسندی هست که از مقاله رضایت داشتین
خیلی ممنونم که این نکته روبا ما به اشتراک گذاشتید درآینده یک مقاله هم درباره ی promise منتشر میکنیم

ارسال دیدگاه
خوشحال میشیم دیدگاه و یا تجربیات خودتون رو با ما در میون بذارید :