אחד היתרונות המובהקים של Node הוא השימוש הרב בפונקציות אסינכרוניות. זה אחד המאפיינים המאפשרים את המהירות והסקלאביליות של שפה זו. אסינכרוניות פותר את צוואר הבקבוק של מערכות רבות ובפרט מערכות web ושרתים: ההמתנה של Thread לפעולת I/O. למשל אם הרצתי שאילתה לDB ואני ממתין לתשובה זה יכול לקחת שניה, או 30 שניות. בזמן הזה הthread תפוס ומבזבז משאבי מערכת למרות שהוא לא עושה כלום (לא מבצע שום פעולה מלבד המתנה).
אמנם בתכנות "רגיל" אפשר ברוב השפות לכתוב קוד אסינכרוני אבל רבים לא טורחים לעשות זאת וספריות וAPI רבים לא תומכים בכך. Node מאלץ את המתכנת לכתוב אסינכרוני כיוון שהקוד רץ בthread אחד ואם תהליך כלשהו ימתין אז כל הפרוסס יהיה תקוע.
כתיבת קוד בסגנון אסינכרוני יכול להיות מבלבל ולא אינטואיטיבי. הבעיה העקרית היא שכמתכנתים אנו רגילים להסתכל על ביצוע שורות קוד לפי סדר כתיבתן. כלומר, שורה 3 תתבצע אחרי שורה 2 וכו’. בתכנות אסינכרוני בדומה לתסנות Multi Threaded המצב שונה.
האתגר הגדול הוא לוודא שהקוד שלנו רץ במקום הנכון ובזמן הנכון. בnod יש לנו 3 אפשרויות לטיפול בפונקציות אסינכרוניות ובפוסט זה ובפוסט הבא נעסוק בהן:
שימוש בפונקציית Callback
שימוש בpromise
שימוש בyield (כשיש תמיכה בES6)
במקור, הדרך הנפוצה היתה שימוש בcallback. הרעיון הוא להעביר פונקציית חזרה לפונקציה האסינכרונית על מנת שזו תקרא לה כשהיא מסיימת את פעולתה. לדוגמא:
db.insert(‘hello’, function(err,res){ console.log(‘Completed inserting to DB’); });
בקוד הנ"ל אני מכניסים אובייקט (רשומה) לדאטהבייס של MongoDB. כאשר הפעולה מסתיימת פונקציית הcallback תקרא ותדפיס את ההודעה.
הבעיה בשיטה הזו היא מה שמכונה callback hell שמתרחש כאשר רוצים לשרשר כמה תגובות אחת לשניה. כלומר, אם היינו רוצים לקרוא לפונקציה אחרת כשהראשונה מסתיימת, ולאחריה
לעוד אחת וכו’ וכולן תלויות זו בזו בסדר זה היה נראה בערך כך:
db.insert({str:‘hello’}, function(err,res){ console.log(‘Completed inserting to DB’); var id = res._id; db.update({id:id, data:’aaa’}, function(err, res){ http.send(‘http://server.com/’ + res, function(err,res){ db.insert({id:id, response:res},function(err, res){ console.log(‘Done!’); }); }); }); });
בעיה נוספת היא שקשה להפריד פונקציונליות למודולים או אפילו פונקציות שונות. פונקציה אסינכרונית אינה יכולה להחזיר ערך. כלומר, היא יכולה אבל הערך יוחזר לפני שהפונקציה תסתיים ולא תתקבל התוצאה הרצויה. המצב הרצוי הוא שהפונקציה תסיים את פעולתה ותחזיר את הערך למי שקרא לה אבל כשפונקציה אסינכרונית מסיימת היא קוראת לפונקציית callback וזו הדרך היחידה להגיב בסיום הפעולה.
לכך המציאו את הpromise. זהו אובייקט שעוטף את הפונקציה האסינכרונית וחושף פונקציה בשם then שאפשר לקרוא עם callback. עדיין יש להעביר callback על מנת שזו תקרא בסיום הפעולה אבל האובייקט Promise ניתן להעברה (שכן זהו אוביקט רגיל לכל דבר) וכך פונקציות אסינכרוניות יכולות להחזיר Promise ולאשר מידול בצורה סבירה.
בדוגמה הקודמת שלנו בשימוש בpromises:
db.insert({str:‘hello’}, function(err,res){ console.log(‘Completed inserting to DB’); var id = res._id; return db.update({id:id, data:’aaa’}).then(function(err, res){ return http.send(‘http://server.com/’ + res).then(function(err,res){ return db.insert({id:id, response:res}.then(function(err, res){ }); }); }); }).then(function(res){ console.log(‘completed’); });
בפוסט הבא נדבר עם yield עם EcmaScript 6 וכיצד ניתן לסנכרן קוד באמצעותו.