Asynchronous Programming (תכנות אסינכרוני) הוא נושא חשוב ורחב היקף אשר נבאר באמצעות סדרת מאמרים זו.
בחלק זה של המדריך נכיר את המושג – "אסינכרוני" ונבין את ההבדל שבין תכנות סינכרוני לתכנות אסינכרוני באמצעות שימוש במספר דוגמאות פשוטות.
בסדרת מאמרים זו נלמד מה הוא תכנות אסינכרוני ולמה אנחנו צריכים אותו.
מדובר בנושא רחב היקף שיש להתעמק בו מתחילתו ועד סופו על מנת להבינו כהלכה,
ולכן סדר הקריאה של מדריך זה הוא חשוב ויש להקפיד עליו.
Asynchronous vs Synchronous
על מנת שנוכל להבין מה זה אסינכרוני, נתאר לנו תרחיש מחיי היום-יום בתחום המסעדנות.
הבעיה שבה נעסוק נקראת בעיית המלצר:
בבית קפה מסוים יש מלצר ובריסטה (מוזג קפה) אשר פועלים בהתאם להוראות שמתקבלות במערכת ממוחשבת.
המערכת פועלת כך שהמלצר לוקח הזמנות משולחנות ומשדר אותם אל הבריסטה,
שמצדו שולח קריאה למלצר במערכת כאשר ההזמנה מוכנה.
כעת, אם נראה זאת במושגים של תכנות, המלצר צריך לחכות שההזמנה תהיה מוכנה ולהגיש אותה על מנת שהוא יוכל להתפנות לקחת הזמנה מהשולחן הבא,
וזאת משום שתכנות מטבעו הוא סינכרוני – כלומר שפונקציות רצות אחת אחרי השנייה לפי הסדר שבו הן נכתבו.
אם כך, על מנת שהפעילות תהיה תקינה נדרשת פעולה אסינכרונית.
הרעיון האסינכרוני הוא שבזמן שהבריסטה מכין את המנה של הלקוח, המלצר יכול לקחת הזמנות נוספות ולשדר אותן אל הבריסטה –
שמצדו יכין את ההזמנות אחת אחרי השנייה, ולא יחכה שהמלצר יאסוף את המנה, אלא ימשיך להכין את המנה הבאה בתור.
חשוב להבין שלעומת זאת, המלצר לא משרת 2 שולחנות בבת אחת, והבריסטה לא מכין 2 מנות באותו הזמן,
אלו פעולות שדווקא כן נרצה שהם יעשו לפי סדר סינכרוני.
שימו לב לתרשים הזרימה הבא אשר יבאר את הנושא:
כלומר שאם נסתכל בתרשים הזרימה, נוכל להבין שמבחינתנו תכנות אסינכרוני אומר שנוכל לעורר (invoke) פונקציה בזמן שנרצה, והתוכנית לא תקפא בתוך הפונקציה, אלא תמשיך לרוץ מבלי לחכות שהפונקציה תסיים את פעולתה.
אסינכרוניות ב-JavaScript
אם נחפש באינטרנט את ההגדרה של שפת JavaScript, נוכן להבחין שזוהי שפה single–threaded.
אם אינכם מכירים את הנושא של תכנות מקבילי תוכלו למצוא סדרת מאמרים על כך בשפת C# בקישור הזה.
כלומר שאם אנחנו מבינים את המושג Thread בודד – אז מתעוררת שאלה.
לרוב, המושג Thread בודד יותר מתקשר עם פעולות מסונכרנות,
אז אם כך, איך זה יכול להיות ששפת JavaScript מסוגלת לבצע פעולות אסינכרוניות?
ראשית נדבר קצת על Stack.
ה-Call Stack בשפת JavaScript הוא מעין "מחסנית" שמכילה מצביעים למיקומי זיכרון.
סידור המחסנית עובד בשיטת "LIFO – (Last In, First Out)",
כלומר שהפונקציה הראשונה שנקרא לה – נמצאת בראש ה-Call Stack,
וזאת משום שהיא הראשונה שתרוץ, כלומר, שבשפת JavaScript סדר הקריאה של הקוד יהיה מלמעלה למטה,
וכך גם סדר הקריאה לפונקציות – כמו שכתבנו אותו, כך הוא ירוץ.
שימו לב לקטע הקוד הבא שידגים זאת :
function fn1(){ console.log(1); } function fn2(){ console.log(2); } function fn3(){ console.log(3); } function fn4(){ console.log(4); } fn1(); fn2(); fn3(); fn4();
כעת לאחר שראינו דוגמא של פעולה סינכרונית, נראה דוגמא לפעולה שהיא אסינכרונית.
באמצעות המתודה setTimeout נוכל להשהות פעולה של פונקציה למשך פרק זמן נתון,
מה שיפה במתודה זו, זה היכולת שלה להשהות את הפעולה מבלי "להקפיא" את התוכנית, זוהי דוגמא קטנה שממחישה מה היה קורה לו רצינו להריץ פונקציה בצורה אסינכרונית:
function fn1(){ console.log(1); } function fn2(){ console.log(2); } function fn3(){ console.log(3); } function fn4(){ console.log(4); } setTimeout(fn1,500); fn2(); fn3(); fn4();
כפי שניתן לראות בתמונה, למרות ש-fn1 היא הפונקציה הראשונה שקראנו לה, היא האחרונה שסיימה את פעולתה, וזאת משום הריצה שלה ארכה כחצי שניה.
היא עשתה את פעולתה בצורה אסינכרונית, כך ששאר הפונקציות רצו עד שהיא סיימה את פעולתה.
סיכום
לסיכום ניתן לומר בפשטות כי הכוונה ב-"אסינכרוני" היא –
"תוכנית אסינכרונית זו טכניקה שמאפשרת לתוכנית שלנו להתחיל ריצת משימת ארוכת פוטנציאל ועדיין להיות מסוגלים להגיב לאירועים אחרים בזמן שהמשימה הזאת עדיין רצה ולא להצטרך לחכות עד שהמשימה תסתיים.
ברגע שהמשימה מסתיימת התוכנית שלנו תציג את התוצאות." – הסבר זה נוסף למאמר על ידי משה שוקר.
כפי שראינו, למרות שקראנו לפונקציה ראשונה היא סיימה את פעולתה אחרונה,
וזאת מבלי להפריע לעכב את התוכנית, כלומר ששאר הפונקציות רצו לפי הסדר שבו הן נקראו בזמן שהיא עשתה את פעולתה.
כעת, לאחר שהבנו את הקונספט נוכל להמשיך אל החלק הבא שבו נעבור על האובייקט promise.