مفهوم برنامه نویسی آسنکرون
در این فصل به معرفی یکی از جذابترین مباحث مربوط به جاوا اسکریپت، یعنی Ajax میپردازیم. همچنین در این فصل به بررسی مفهوم برنامهنویسی آسنکرون و امکانات جاوا اسکریپت در این زمینه میپردازیم. در واقع Ajax یک حالت خاص از برنامهنویسی آسنکرون در جاوا اسکریپت است.
آنچه در این فصل میآموزید :- آشنایی با مفهوم برنامهنویسی آسنکرون (Asynchronous Programming)
- آشنایی با پروتکل HTTP، نحوهی عملکرد وبسایتها و مدل Client-Server
- آشنایی با Ajax و شئ XMLHttpRequest
- آشنایی با مفهوم جهنم Callback یا Callback Hell
- آشنایی با Promise ها برای رفع مشکل جهنم Callback
- آشنایی با Fetch API
- آشنایی با توابع آسنکرون و کلمات کلیدی async و await
برنامه نویسی آسنکرون چیست؟
حتماً به یاد دارید که در فصل هفتم با مفهوم برنامهنویسی رویداد محور آشنا شدیم. در واقع برنامهنویسی رویداد محور، حالت خاصی از برنامهنویسی آسنکرون است. در این بخش قصد داریم این موضوع را با جزئیات بیشتری بررسی کنیم. برای شروع دستورات زیر را در نظر بگیرید.
console.log(1);
console.log(2);
myFunction();
console.log(3);
function myFunction(){
console.log("I am a function");
}
اگر دستورات فوق را اجرا کنید، خروجی زیر تولید خواهد شد.
← 1
← 2
← "I am a function"
← 3
در صورتی که این دستورات را مجدداً اجرا کنید، باز هم همین خروجی تولید خواهد شد. زیرا ترتیب اجرای دستورات کاملاً مشخص است. یعنی پیش از اجرا برنامه، کاملاً میدانیم که دستورات با چه ترتیبی اجرا میشوند. این نوع برنامهنویسی را در اصلاح برنامهنویسی سنکرون (Synchronous) یا همگام مینامیم.
به عبارت دیگر ترتیب اجرای دستورات در برنامههای سنکرون، فقط به ترتیب نوشتن دستورات وابسته است و عوامل خارجی در آن بی تاثیر هستند. البته در شرایطی که از ساختارهای شرطی و کنترلی در برنامه استفاده شده باشد. ممکن است روند اجرای دستورات در اجراهای مختلف یکسان نباشد. اما در این شرایط نیز، کافی است مقدار متغیرهای به کار رفته در ساختارهای شرطی و کنترلی را بدانیم. در این صورت میتوانیم ترتیب اجرای دستورات را تعیین کنیم. به عنوان مثال دستورات زیر را در نظر بگیرید.
let a = 10;
if(a < 20){
console.log("a is less than 20.");
}else{
console.log("a is equal or greater than 20.");
}
با اجرای دستورات فوق میدانیم که دستور موجود در خط 5 اجرا نخواهد شد. زیرا مقدار a کوچکتر از 20 است. در نتیجه بخش if اجرا شده و دستور موجود در خط 3 اجرا خواهد شد. اما میدانیم که با تغییر مقدار متغیر a، میتوان حالتی را به وجود آورد که در آن دستور خط 3 اجرا نشود و دستور خط 5 اجرا شود. اما در هر حالتی با دانستن مقدار متغیر a، دقیقاً میدانیم که کدامیک از دستورات اجرا خواهد شد. هرچند در این مثال فقط دو دستور برای تولید خروجی وجود دارد. اما حتی با بیشتر شدن تعداد دستورات و تعداد ساختارهای شرطی و کنترلی، باز هم با دانستن مقدار متغیرها، دقیقاً میتوان ترتیب اجرای دستورات را مشخص کرد. پس این برنامه نیز باید در دستهی برنامههای سنکرون قرار گیرد.
با توجه به مشخص بودن ترتیب اجرای دستورات در برنامههای سنکرون، به سرعت میتوان گزارهی زیر را نتیجه گرفت.
در برنامههای سنکرون، تا زمانی که یک دستور به طور کامل اجرا نشود، دستور بعدی اجرا نخواهد شد.
نکتهی کلیدی برای تعیین سنکرون بودن یا نبودن یک برنامه، گزارهی فوق است. یعنی برنامههایی وجود دارند که گزارهی فوق در مورد آنها صدق نمیکند. به چنین برنامههایی در اصطلاح برنامههای آسنکرون (Asynchronous) یا غیر همگام گفته میشود. ویژگی برنامههای آسنکرون دقیقاً معکوس برنامههای سنکرون است. یعنی به بیان دیگر میتوان گفت :
- در برنامههای آسنکرون ترتیب اجرای دستورات از قبل مشخص نیست و وابسته به عوامل خارجی است.
- در برنامههای آسنکرون برای اجرای یک دستور، نیازی به اجرای کامل دستور قبلی نیست.
البته باید این نکته را نیز متذکر شد که تقریباً هیچ برنامهای به صورت کاملاً آسنکرون اجرا نمیشود. یعنی کدهای یک برنامه معمولاً ترکیبی از کدهای سنکرون و آسنکرون هستند. در واقع در حالت عادی، دستورات یک برنامهی جاوا اسکریپت به صورت سنکرون اجرا میشوند. اما برنامهنویس میتواند بخشی از کدها را عمداً به صورت آسنکرون پیادهسازی کند.
حال سوال این است که عوامل خارجی چطور میتوانند در روند اجرای یک برنامهی آسنکرون دخیل باشند؟ همانطور که اشاره شد پیش از این نیز بارها چنین برنامههایی را نوشتهایم. در فصل هفتم تعداد زیادی مثال از برنامهنویسی رویداد محور نشان داده شد. در تمام آن مثالها عوامل خارجی در ترتیب اجرای دستورات نقش داشتند. به عنوان مثال کدهای زیر را در نظر بگیرید.
const button1 = document.getElementById('button1');
const button2 = document.getElementById('button2');
console.log(1);
button1.addEventListener('click' , function(){
console.log('Button1 is clicked');
});
button2.addEventListener('click' , function(){
console.log('Button2 is clicked');
});
console.log(2);
در این مثال فرض شده است که دو دکمه در صفحهی وب وجود دارد که صفت id آنها به ترتیب "button1" و "button2" است. توجه کنید که در این مثال از برنامهنویسی سنکرون و آسنکرون به صورت همزمان استفاده شده است. در واقع فقط دو تابعی که به عنوان Event Handler به کار رفتهاند به صورت آسنکرون اجرا میشوند. و سایر دستورات به صورت سنکرون و با همان ترتیب نوشته شده اجرا خواهند شد. به همین دلیل در صورت اجرای برنامهی فوق خواهید دید که همیشه دستور موجود در خط 3 زودتر از دستور موجود در خط 11 اجرا میشود. و هر دو دستور همیشه زودتر از دو دستور موجود در خطوط 5 و 9 اجرا میشوند.
اما ترتیب اجرای دستورات خطوط 5 و 9 قابل پیشبینی نیست. زیرا اجرای این دستورات به عوامل خارجی وابسته است. یعنی این دستورات فقط در صورتی اجرا میشوند که کاربر روی دکمههای موجود در صفحهی وب کلیک کند. و با توجه به اینکه ترتیب و تعداد کلیکهای کاربر بر روی این دکمهها مشخص نیست. نمیتوان تعیین کرد که کدام دستور زودتر اجرا میشود. حتی ممکن است هیچکدام از این دستورات اجرا نشوند. زیرا ممکن است کاربر روی هیچ دکمهای کلیک نکند. اما دستورات خطوط 3 و 11 همیشه اجرا خواهند شد. زیرا اجرای آنها به عوامل خارجی وابسته نیست. این مثال را میتوانید اینجا در CodePen اجرا کنید.
همچنین گفته شد که در برنامههای آسنکرون، اجرای هر دستور وابسته به اجرای کامل دستور قبلی نیست. در مثال فوق نیز میبینید که اجرای دستور خط 11، وابسته به اجرای دستورات خطوط 5 و 9 نیست. اما اجرای دستور خط 11 به اجرای دستور خط 3 وابسته است. زیرا این بخش از کدها به صورت سنکرون نوشته شده است. یعنی تا زمانی که دستور خط 3 اجرا نشود، دستورات بعدی نمیتوانند اجرا شوند.
نمونهی دیگری از برنامهنویسی آسنکرون که پیش از این با آن آشنا شدهایم، زمانبندی کارها در جاوا اسکریپت است. حتماً به یاد دارید که در فصل نهم در مورد زمانبندی کارها با استفاده از توابع setTimeout و setInterval صحبت کردیم. به عنوان مثال کدهای زیر را در نظر بگیرید.
console.log(1);
setTimeout(function(){
console.log('Time Out');
} , 2000);
console.log(2);
با اجرای این دستورات خروجی زیر تولید خواهد شد.
← 1
← 2
← "Time Out"
مشاهده میکنید که دستور خط 5 زودتر از دستور خط 3 اجرا شده است. یعنی دستور خط 3 به صورت آسنکرون اجرا میشود. زیرا با اجرای تابع setTimeout، یک زمانسنج برای 2 ثانیه تنظیم میشود. و پس از به پایان رسیدن 2 ثانیه دستور خط 3 اجرا میشود. یعنی اگر بعد از تابع setTimeout تعداد زیادی دستور در برنامه وجود داشته باشد. باز هم بدون توجه به تعداد و ترتیب اجرای این دستورات، دستور خط 3 بعد از 2 ثانیه اجرا خواهد شد.
سوال دیگری که در اینجا مطرح میشود این است که آیا برنامهنویسی آسنکرون مزیتی نسبت به برنامهنویسی سنکرون دارد یا خیر؟ در پاسخ به این سوال باید گفت که این بستگی به شرایط و اهداف برنامهی مورد نظر دارد. در اکثر برنامهها بیشتر دستورات به صورت سنکرون اجرا میشوند. حتی در بعضی برنامهها تمام دستورات به صورت سنکرون اجرا میشوند. زیرا اولاً سختافزار کامپیوتر به طور طبیعی برای اجرای برنامههای سنکرون طراحی شده است. و ثانیاً نوشتن برنامههای سنکرون و درک آن برای انسان بسیار سادهتر است.
اما در برخی مواقع بهتر است برنامهها به صورت آسنکرون نوشته شوند. دلیل اصلی استفاده از برنامهنویسی آسنکرون جلوگیری از اتلاف وقت در اجرای برنامهها است. مثلاً در مثال فوق اگر برنامه به صورت سنکرون نوشته شود. با اجرای تابع setTimeout، باید اجرای برنامه به مدت 2 ثانیه متوقف شود. سپس دستور خط 3، و بعد از آن دستور خط 5 اجرا شود.
حال فرض کنید در یک برنامه از تابع setInterval استفاده کردهایم. اگر تمام کدهای این برنامه به صورت سنکرون اجرا شوند. باید به صورت مکرر اجرای برنامه متوقف شود. در نتیجه عملاً اجرای برنامه ممکن نیست. پس مزیت اصلی برنامههای آسنکرون این است که بخشی از کدها را فقط در شرایط خاصی اجرا میکنند. یعنی برخلاف برنامههای سنکرون برای اجرای هر دستور، منتظر اجرای دستور قبلی نمیمانند. بلکه از روی برخی دستورات عبور میکنند و در صورت فراهم شدن شرایط خاصی آنها را اجرا میکنند. لازم به ذکر است که زمان فراهم شدن شرایط برای اجرای کدهای آسنکرون ممکن است قابل پیشبینی باشد (مانند تابع setTimeout) و یا قابل پیشبینی نباشد (مانند کلیک کردن کاربر بر روی یک دکمه).
همچنین توجه داشته باشید که همیشه نمیتوان برنامهها را به صورت آسنکرون نوشت. یعنی برخی دستورات باید به صورت سنکرون اجرا شوند. زیرا اجرای صحیح برخی از دستورات، وابسته به اجرای کامل و صحیح دستورات قبلی است. به عنوان مثال دستورات زیر را در نظر بگیرید.
let a = 2 , b = 3 , c = 1;
let delta = b ** 2 - 4 * a * c;
r1 = (-b + Math.sqrt(delta)) / (2 * a);
r2 = (-b - Math.sqrt(delta)) / (2 * a);
console.log("The polynomial roots are : " + r1 + " and " + r2);
← "The polynomial roots are : -0.5 and -1"
این برنامه با داشتن ضرایب یک چند جملهای درجه 2، ریشههای چند جملهای را محاسبه کرده و در کنسول نمایش میدهد. با کمی دقت متوجه خواهید شد که اجرای صحیح هر یک از دستورات فوق، به اجرای کامل و صحیح دستور قبلی وابسته است. بنابراین نوشتن چنین برنامهای به صورت آسنکرون ممکن نیست. و این برنامه حتماً باید به صورت سنکرون نوشته و اجرا شود. البته دستورات خطوط 3 و 4 به یکدیگر وابسته نیستند و میتوانند با ترتیب دلخواه اجرا شوند. اما به هر حال برای اجرای دستور خط 5، باید تمام دستورات قبلی به شکل صحیح اجرا شده باشند.
البته پیش از این نیز با بیشتر نکاتی که در این بخش مطرح شدند آشنا بودیم. و در مثالهای زیادی از کدهای سنکرون و آسنکرون استفاده کرده بودیم. اما در این فصل قصد داریم از برنامهنویسی آسنکرون به اشکال دیگری نیز استفاده کنیم. در واقع آنچه که در این فصل نسبت به فصول قبل متفاوت است، نوع عوامل خارجی در برنامهنویسی آسنکرون است.
در اکثر مثالهایی که در فصل هفتم ارائه شدند، عامل خارجی کاربر بود. به عنوان مثال کاربر با تولید رویدادهایی مانند click یا mousedown یا keypress موجب اجرای آسنکرون بخشی از کدهای برنامه میشد. البته در برخی موارد نیز رویدادها توسط مرورگر تولید میشدند و کاربر دخالتی در آنها نداشت. به عنوان مثال رویدادهای load یا DOMContentLoaded توسط مرورگر تولید میشوند.
آنچه که در این فصل تحت عنوان برنامهنویسی آسنکرون معرفی خواهد شد، در واقع نوع خاصی از برنامهنویسی رویداد محور است. تفاوت اصلی این فصل با فصول قبلی در نوع رویدادها است. رویدادهایی که در این فصل با آنها سر و کار داریم عمدتاً در تعامل مرورگر و سرور تولید میشوند. یعنی مرورگر درخواستی را به سرور ارسال میکند و پاسخی را از سرور دریافت میکند. و در حین انجام این فرآیند انواع مختلفی از رویدادها رخ میدهند که در این فصل به بررسی آنها خواهیم پرداخت. البته باید توجه داشته باشید که در حالت کلی برنامهنویسی آسنکرون، زیرمجموعهی برنامهنویسی رویداد محور نیست. یعنی برنامههای آسنکرون را میتوان به شکلی نوشت که رویداد محور نباشند. اما تمام برنامهها و مثالهایی که در این فصل خواهیم دید، رویداد محور هستند.
اما قبل از اینکه وارد بحث اصلی این فصل شویم، لازم است کمی در مورد نحوهی عملکرد شبکهی اینترنت و وب صحبت کنیم. زیرا سرورهایی که وبسایتها و صفحات وب را نگهداری میکنند متصل به شبکهی اینترنت بوده و بر پایهی پروتکلی (قرارداد) به نام HTTP کار میکنند. پس قبل از اینکه به نحوهی تبادل اطلاعات بین مرورگر و سرور در جاوا اسکریپت بپردازیم. لازم است کمی با جزئیات پروتکل HTTP و نحوهی برقراری ارتباط با سرورها در شبکهی اینترنت آشنا شویم. به همین دلیل در بخش بعدی به بررسی پروتکل HTTP خواهیم پرداخت.