eido300 פורסם 2020 בדצמבר 30 Share פורסם 2020 בדצמבר 30 הי אשמח לדעת קצת יותר על הנ"ל אני יודע שsync מכריח שרק theard הנוכחי ירוץ, כל השאר יחכו שהוא יסיים. async מריץ את הtherad במקביל לתהליכים אחרים, מה הולך עם הtask, await, lock? ותכלס, איך כל זה נכנס בעולם האמיתי, כשאני בונה תוכנה, מתי אצטרך להשתמש בזה? עוד לא יצא לי להתקל בצורך כזה (חוץ מאולי לשלוח מייל שזה תוקע את המחשב, וגם לו יש פונקציה מובנית בשביל זה). תודה. ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
af db creid פורסם 2020 בדצמבר 30 Share פורסם 2020 בדצמבר 30 שאלה גדולה ותשובה ארוכה, אנסה להתפנות לכתוב תשובה בהמשך היום. בקצרה, זה ממש הפוך (בהנחה שבasync אתה מתכוון לasync/await ולא סתם asynchronous programming): אם אתה מבצע קוד סינכרוני, הthread הנוכחי נחסם ולא יכול לבצע קוד אבל threadים אחרים ממשיכים לרוץ במקביל. אם אתה מבצע קוד async (עם await), גם בthread הנוכחי וגם בthreadים אחרים יכול לרוץ קוד. 1 ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
eido300 פורסם 2020 בדצמבר 30 מחבר Share פורסם 2020 בדצמבר 30 ציטוט של af db creid שאלה גדולה ותשובה ארוכה, אנסה להתפנות לכתוב תשובה בהמשך היום. בקצרה, זה ממש הפוך (בהנחה שבasync אתה מתכוון לasync/await ולא סתם asynchronous programming): אם אתה מבצע קוד סינכרוני, הthread הנוכחי נחסם ולא יכול לבצע קוד אבל threadים אחרים ממשיכים לרוץ במקביל. אם אתה מבצע קוד async (עם await), גם בthread הנוכחי וגם בthreadים אחרים יכול לרוץ קוד. תודה רבה התכוונתי לasync/await אבל יכול להיות שערבבתי קצת עם asynchronous programming... ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
eido300 פורסם 2020 בדצמבר 31 מחבר Share פורסם 2020 בדצמבר 31 ציטוט של af db creid שאלה גדולה ותשובה ארוכה, אנסה להתפנות לכתוב תשובה בהמשך היום. אני מקווה שלא שכחת אותי ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
af db creid פורסם 2020 בדצמבר 31 Share פורסם 2020 בדצמבר 31 לא התפניתי לזה. תסלח לי אני מקווה, יש לי עוד כמה דברים חוץ מהפורום הזה לא שכחתי. 1 ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
eido300 פורסם 2020 בדצמבר 31 מחבר Share פורסם 2020 בדצמבר 31 מצפה בכיליון עיניים ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
תגובה פופולרית af db creid פורסם 2021 בינואר 1 תגובה פופולרית Share פורסם 2021 בינואר 1 (נערך) טוב יאללה. בוא נתחיל מthreads וlock, זה יותר פשוט. threads משמשים בתפקיד מאוד פשוט: הרצת קוד במקביל. ניקח כדוגמה את Word. מעבד התמלילים שומר את המסמך כל עשר דקות (ניתן לקינפוג). עכשיו, נניח שיש לי מסמך ענק, עם המון גרפיקה. כמה ענק? היו לי מצגות בעבר שלקח כמה דקות לשמור. אני ממש לא רוצה שסאמצע שאני עובד, בדיוק אחרי שעלה לי איזה רעיון מדליק (כי בכל זאת, חוק מרפי) הWord יתקע פתאום למשך שתי דקות בניסיון נואש לשמור את המסמך למקרה של הפסקת חשמל או מי יודע מה. אם דבר כזה היה קורה לי... לא הייתי מקנא במתכנתים של Microsoft. בכל מקרה, זו הסיבה שWord מפעיל את השמירה האוטומטית בthread נפרד. ככה אני לא אתקע (ליתר דיוק, לא ארגיש שאני נתקע. עוד על זה בקרוב), אבל המסמך יישמר למקרה שמרפי יחליט להציג את כוחו. עכשיו, יש שתי סיבות להריץ קוד במקביל. למעשה, שלוש, אבל על השלישית אדבר בהמשך. הראשונה היא, אם אני רוצה ששתי פעולות יבוצעו "כאילו במקביל". זו הדוגמה של הWord, ושלא במפתיע, הרבה פעולות שעלולות לתקוע את הUI מבוצעות בthread נפרד. למעשה, כל פעולה שיכולה לקחת יותר מכמה אלפיות השנייה כדאי לבצע בthread נפרד. השנייה היא חישוב מסובך, אלגוריתמית. נגיד שאני צריך לבדוק אם מספר גדול מאוד הוא ראשוני, ואני רוצה לעשות את זה בשיטה הנאיבית של בדיקת כל המספרים עד השורש אם הם מחלקים. למה בשיטה הגרועה הזו? כי אני אוהב לעצבן את הלקוחות שלי. הבעיה היא, שאומנם אני אוהב לעצבן את הלקוחות, אבל לא את הבוס (בכל זאת, הוא יכול לפטר). אז אני מחלק את העבודה: אני אוציא נגיד 4 threads, כל אחד יבדוק רבע אחר של המספרים. האם זה יועיל או אולי יזיק? תלוי, כמובן. התשובה הנצחית. אם יש לך מספיק ליבות במעבד, מערכת ההפעלה תשמח לתת לכל thread ליבה משלו, ואז כמובן הדברים יהיו מהירים יותר. אבל אם לא, מערכת ההפעלה עדיין תרצה לרצות אותך, ולכן היא תשתמש בליבה אחת לכמה threads: כל קצת זמן היא תחליף ביניהן, מה שנקרא context switch. ככה זה נראה שהדברים מתבצעים במקביל. זה עוזר לבעיה הראשונה (של הWord), אבל לא לשנייה. הcontext switch יאט את העבודה, כי למעבד יש עוד עבודה עכשיו לבצע. לכן בבעיה הזו בד"כ בודקים כמה ליבות יש למעבד ועושים spawn באותו מספר threads. יש שפות תכנות\סביבות עבודה שעושות את זה אוטומטית, כמו Go. שים לב שבד"כ במחשב רצות עוד כמה עשרות תוכניות, וכל אחת גם רוצה זמן מעבד, וזה עשוי להשפיע על העניין. הכל טוב ויפה. אז מה זה lock? או, זה קשור לשאלת השאלות: המלצר (לא צוחק. יש על זה בעיה של דייקסטרה עם פילוסופים ומלצר). בוא ניקח דוגמה פשוטה יותר. תריץ את קוד הC# הבא (לפני זה תוודא שאתה מבין אותו, למקרה שלא ידעת Task.Run מריץ thread חדש (לא מדויק אבל לא קריטי) וTask.WaitAll חוסם (blocks) את הthread הנוכחי עד שכל הthreads המצוינים הסתיימו): using System; using System.Threading.Tasks; public static class Program { private static int _Counter = 0; public static void DoWork() { for (int i = 0; i < 1_000_000; i++) { _Counter++; } } public static void Main() { Task t1 = Task.Run(DoWork); Task t2 = Task.Run(DoWork); Task.WaitAll(t1, t2); Console.WriteLine("{0:n0}", _Counter); } } הכנתי לך dotnetfiddle כאן. תיכנס לקישור וRun. אני מחכה. ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ טוב. הרצת? תריץ שוב. ושוב. ושוב. עשר פעמים. שיב לב לתוצאה! ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ מה קרה כאן? לא רק שהתוצאה היא לא 2 מיליון כמצופה, היא משתנה בין פעמים! מה נסגר?? אז כדי ממש להבין מה נסגר, צריך להיכנס לאסמבלי. אל תדאג, לא אתעלל בך. אשאיר את האסמבלי לפעם אחרת. במקום זאת אנסה להסביר מה קרה במילים (ההסבר פשטני במכוון, אל תטיפו לי על זה, אני יודע): כשאתה עושה ++, המעבד לא ממש יכול להיכנס לזיכרון ולהוסיף אחד. זה עניין מסובך יותר. קודם כל, צריך לקחת את הערך מהזיכרון. אחר כך, תוסיף 1. בסוף, תחזיר לזיכרון את הערך החדש. עכשיו, אם מבצעים את זה בלולאה, זה ממש בזבוז. למה לטעון כל פעם את הערך? עדיף לטעון פעם אחת בהתחלה ולשים פעם אחת בסוף. בוא נדמיין את זה: אני מתחיל את thread 1. הוא מתחיל לרוץ. טוען את _Counter מהזיכרון. מוסיף 1. מוסיף 1. מוסיף 1. מוסיף 1. מוסיף 1. מוסיף 1. מוסיף 1. .... טוב. הגיע תורו של thread 2. הוא מתחיל לרוץ. טוען את _Counter. נגיד שבשלב הזה thread 1 בוצע כבר 50 אלף פעמים, אז _Counter מכיל 50 אלף. מוסיף 1. מוסיף 1. מוסיף 1. מוסיף 1. מוסיף 1. מוסיף 1. ... בחזרה לthread 1. מוסיף 1 - ל_Counter שלו שהוא עדיין 50 אלף! מוסיף 1. מוסיף 1. מוסיף 1. ... וככה זה הולך. אם thread 1 יסיים ראשון, התוצאה תהיה מיליון. אם 2, התוצאה תהיה מיליון וחמישים אלף. עכשיו אפשר גם להבין למה הערכים השתנו בין הרצות: זה תלוי על איזה ליבה כל thread רץ - אולי במקרה thread 2 רץ על ליבה מהירה יותר, ולכן יסיים ראשון ויפסיד במירוץ? (כן כן, ראשון. זו לא טעות. תקרא שוב ותבין). זה תלוי בטמפרטורה של המחשב, במהירות הליבות, באם האנטי ווירוס מריץ סריקה בדיוק עכשיו ובאלף ואחת דברים נוספים. מירוץ. זה בדיוק מה שקורה כאן. זו גם הסיבה לשם של המונח: race condition. אל תזלזל בעניין. מרכזיית הטלפונים של Bell (אם אני זוכר נכון, לא הצלחתי למצוא בגוגל) קרסה בגלל race condition בלתי צפוי. ב1985 עד 1987 מספר אנשים מתו מסרטן בגלל race condition (מקור). איך מונעים את העניין? אנחנו צריכים איזו דרך להגיד למעבד: "חביבי, אל תמהר לשום מקום. אם מישהו אחר עסוק עם העניין עכשיו, תרגיע. חכה שהוא יסיים". ובכן, למרבה המזל, יש דרך כזו. הרבה, למען האמת. נתעסק באחת: locking. הרעיון הוא כזה: אנחנו יוצרים אובייקט שבד"כ נקרא mutex (ר"ת של mutual exclusion) או שם דומה. לפני שניגשים למשאב המשותף (_Counter, במקרה שלנו), אנחנו "נועלים" את הmutex, ומשחררים אותו כשאנחנו מסיימים. אם אנחנו מנסים לנעול mutex שכבר נעול ע"י thread אחר, הthread שלנו נחסם עד שהאחר משחרר את המנעול שלו. זה לא תופס זמן מעבד: זה מבוצע ברמת מערכת ההפעלה ובמידה מסוימת אפילו המעבד. הינה זה בC#: using System; using System.Threading; using System.Threading.Tasks; public static class Program { private static int _Counter = 0; private static Mutex _Mutex = new Mutex(); public static void DoWork() { for (int i = 0; i < 1_000_000; i++) { _Mutex.WaitOne(); _Counter++; _Mutex.ReleaseMutex(); } } public static void Main() { Task t1 = Task.Run(DoWork); Task t2 = Task.Run(DoWork); Task.WaitAll(t1, t2); Console.WriteLine("{0:n0}", _Counter); } } וכרגיל יש קישור כאן. יש לC# כלי להפוך את זה לנוח יותר: המשפט lock: using System; using System.Threading.Tasks; public static class Program { private static int _Counter = 0; private static object _CounterLock = new object(); public static void DoWork() { for (int i = 0; i < 1_000_000; i++) { lock (_CounterLock) { _Counter++; } } } public static void Main() { Task t1 = Task.Run(DoWork); Task t2 = Task.Run(DoWork); Task.WaitAll(t1, t2); Console.WriteLine("{0:n0}", _Counter); } } והקישור: https://dotnetfiddle.net/UJENnV. זה מיתרגם לקוד הבא: using System; using System.Threading; using System.Threading.Tasks; public static class Program { private static int _Counter = 0; private static object _CounterLock = new object(); public static void DoWork() { for (int i = 0; i < 1_000_000; i++) { bool lockWasTaken = false; try { Monitor.Enter(_CounterLock, ref lockWasTaken); _Counter++; } finally { if (lockWasTaken) { Monitor.Exit(_CounterLock); } } } } public static void Main() { Task t1 = Task.Run(DoWork); Task t2 = Task.Run(DoWork); Task.WaitAll(t1, t2); Console.WriteLine("{0:n0}", _Counter); } } https://dotnetfiddle.net/L7Z5QW. מכאן אתה יכול להבין למה הייתי צריך אובייקט נפרד מ_Counter לlock - צריך reference type, כי אחרת הוא יועתק ולא נוכל לרכוש מנעול. ואחרון חביב, למקרים נפוצים כמו פעולות אריתמטיות יש לנו את המחלקה Interlocked: using System; using System.Threading; using System.Threading.Tasks; public static class Program { private static int _Counter = 0; public static void DoWork() { for (int i = 0; i < 1_000_000; i++) { Interlocked.Increment(ref _Counter); } } public static void Main() { Task t1 = Task.Run(DoWork); Task t2 = Task.Run(DoWork); Task.WaitAll(t1, t2); Console.WriteLine("{0:n0}", _Counter); } } https://dotnetfiddle.net/Ks6Tt1. זה גם מהיר יותר כי locking זה איטי, וInterlocked מיתרגם להוראה ברמת המעבד (לפחות בx86). הערה חשובה: רוב המחלקות ב.NET, כולל מחלקות האוספים, אינן thread-safe. יש לנו את System.Collections.Concurrent עבור מחלקות אוספים שהן כן. טוב, די לעכשיו. כתבתי הרבה. על async/await בהמשך... המשך יבוא. נערך 2021 בינואר 1 על-ידי af db creid 3 ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
eido300 פורסם 2021 בינואר 1 מחבר Share פורסם 2021 בינואר 1 וואוו חתיכת עבודה עשית פה, מקווה שלא נשארת ער בשביל זה כל הלילה... ממש תודה רבה ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
eido300 פורסם 2021 בינואר 5 מחבר Share פורסם 2021 בינואר 5 חשבתי על זה שבגלל שאתה לא עושה את זה async ביחד עם שאר הדברים, אני await עם התשובה... 1 ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
af db creid פורסם 2021 בינואר 6 Share פורסם 2021 בינואר 6 טוב. אמנם יש לי עוד הרבה דברים לעשות, אבל הגעתי למסקנה שזה לא יפה שאני דוחה אותך ככה, אז אשב לכתוב את הפוסט ואגמור עם זה. הסיבה שאנחנו רוצים async/await היא כדי להקל עלינו את הכתיבה, ויותר חשוב, הקריאה של קוד. אבל זה לא עונה על השאלה הבסיסית יותר: למה לא להשתמש בthreads? ומה ההבדל בין threads לasync/await או מה שקדם לו (ספציפית, callback-style וPromises לjs וTPL ל.NET)? בוא נתחיל עם js, כי היא פשוטה יותר להבין. JS היא single-threaded. זה אומר שיש אך ורק thread אחד ולא יותר (כיום זה כבר לא נכון, אבל עדיין זה המודל הבסיסי ופעם זה מה שהיה). מדוע? יש מספר יתרונות למודל הזה: אל"ף, הוא מפשט בהרבה את מנועי הJS, כי ככה הם לא צריכים לדאוג לthread safety. אפילו מפתחי Python, שיש להם ספריית threading, יש להם GIL (ר"ת של Global Interpreter Lock) שפירושו שהמפרש של פייתון אינו thread-safe (דבר שהיה מאט אותו בצורה ניכרת), ולכן threads לא באמת מריצים קוד במקביל. אם אתה רוצה הרצת קוד במקביל אתה צריך להשתמש במודול multiprocessing שבמקום threads מוציא processes (שיקרים הרבה יותר). שנית, בל נשכח שJS נולדה עבור הדפדפנים כשפה לאנימציות ולא הרבה יותר. מקסימום אימות של טפסים. אני לא חושב שמתכנני השפה האמינו שהיא תהפוך להיות השפה הנפוצה בעולם... אבל לצד שרת, אף אחד לא חלם אפילו להשתמש בJS. והיתה לזה סיבה טובה. שרתים מוכרים, כמו PHP וASP (וגם ASP.NET), פעלו בדרך של threads: כל בקשה שנשלחת לשרת מקבלת thread משלה. ככה אפשר לטפל בכל בקשה בנפרד, בלי להסתבך עם בקשות אחרות. אבל לדרך הזו היה חיסרון בולט: כמו שאמרתי, context switch. זה דבר יקר. שרתים בתקופה הזו יכלו לטפל, בממוצע, בכעשרת אלפים בקשות בשנייה. אתה מקבל יותר בקשות? תוסיף שרת, בבקשה. ב2009 קם מתכנת בשם Ryan Dahl והכריז: אני רוצה ליצור את Node.js, JS עבור שרתים. אבל היא לא תעבוד בדרך הרגילה, כי בJS אין threads, כזכור. במקום זאת, הוא אמר: מהו הדבר האיטי ביותר ששרתים מבצעים? שליחת קריאות רשת. ואחריו? קריאה מהדיסק. עד כמה זה יותר איטי? ובכן, אם גישה למטמון L1 הייתה לוקחת שנייה אחת - רק שנייה אחת! אזי קריאת 4 קילובייט אקראיים מSSD הייתה לוקחת רבע שעה. לא פחות! ונחש כמה זמן היה לוקח לשלוח פקטה - אחת! מקליפורניה להולנד ובחזרה? ספוילר: יותר מ13 וחצי שנים!!! למזלנו, גישה לL1 לא לוקחת שנייה אלא כחצי מיליארדית השנייה. אבל הפקטור הוא אותו פקטור. מה שמאט את השרת זה לא החישובים שלו - זה התקשורת עם העולם החיצון. והוא עושה הרבה מזה: כל שליחת תגובה זה שליחת הרבה פקטות. ובשביל כל תגובה כזו, אנחנו קוראים מהדיסק את הtemplate (או מהRAM, אבל כנראה מהדיסק). חשב Ryan לעצמו, האם יש אלטרנטיבה? ובכן, יש: והיא אפילו היתה קיימת כבר הרבה הרבה זמן. קוראים לה Event Driven Programming. מהו Event Driven Programming? ובכן, נניח שאני מממש GUI. ויש לי שני כפתורים. עכשיו, אני רוצה לעשות משהו כשהכפתור נלחץ. דרך פשוטה היא להשתמש בלולאה: while (true) { if (button1.Clicked) { // Handle click... } } אבל בגישה הזו יש חיסרון בולט: אני לא יכול לטפל במקביל בשני הכפתורים. אז אפשר להוסיף thread נוסף, אבל זה יקר. במקום זאת, אפשר להוסיף עוד if: while (true) { if (button1.Clicked) { // Handle button1 click... } if (button2.Clicked) { // Handle button2 click... } } אבל רגע, זה לא איך שאנחנו עובדים עם ספריות GUI מודרניות! נכון. כי זה מסובך ומועד לטעויות. במקום זאת, הספריה שמה לולאה כזו מאחורי הקלעים, ונותנת לנו לרשום callbacks. מהו callback? זוהי פונקציה שנקרא לה כאשר מאורע מסוים מתרחש. בC# זה ממומש באמצעות delegates או events. ככה: public class MyForm : System.Windows.Forms.Form { private System.Windows.Forms.Button _Button1; private System.Windows.Forms.Button _Button1; public MyForm() { InitializeComponent(); } private void InitializeComponent() { _Button1 = new System.Windows.Forms.Button(); _Button2 = new System.Windows.Forms.Button(); Controls.Add(_Button1); Controls.Add(_Button2); // Some more stuff... _Button1.Click += Button1_Click; _Button2.Click += Button2_Click; } private void Button1_Click(object sender, System.EventArgs e) { System.Windows.Forms.MessageBox.Show("Button 1 clicked!"); } private void Button2_Click(object sender, System.EventArgs e) { System.Windows.Forms.MessageBox.Show("Button 2 clicked!"); } } מאחורי הקלעים, כך (כמובן שזה ממש מפושט) נראה הקוד של WinForms: while (true) { Message message = GetMessage(); switch (message.Type) { case MessageType.MouseLeftButtonClick: foreach (Button button in _Buttons) { if (IsInsideRegion(button.Region, message.MousePosition)) { button.Click?.Invoke(button, EventArgs.Empty); } } break; // More events... } } ושוב, למעשה זה לא עובד ככה, אבל זה העיקרון. JS, במיוחד, מתמחית בEvent-Driven Programming, ומתכנתי JS הכירו מאז ומתמיד את המושג callback. לדוגמה, form.submit זה אירוע (event). אבל callbacks לא שימשו רק לזה: לדוגמה, איך אני מושך מידע מהאינטרנט בPython? import requests # An external pip library, very common though url = 'https://google.com' res = requests.get(url) res = res.text print(res) אבל זה blocking operation: בזמן שהקוד הנ"ל מחכה לתשובה, שום דבר לא קורה. בJS אי אפשר לעשות דבר כזה, כי גם הHTML רץ באותו thread: אם נחסום את הthread, הדף יקפא ולא יוכל לבצע שום אינטרקציה עם המשתמש. זו כמובן לא חוויה ידידותית במיוחד, ולכן משתמשים בcallbacks (הערה חשובה: זה אינו קוד JS מודרני, אלא איך שנכתב בעבר): var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4 /* Completed */) { if (xhr.status === 200 /* HTTP OK */) { var response = xhr.responseText; // Process response... } else { alert("An error occurred"); } } }; xhr.open('GET', '/my-page'); xhr.send(); כמו שאתה רואה, אנחנו לא מחכים שהבקשה תסתיים: במקום זאת, אנחנו אומרים לדפדפן "כשהבקשה מסתיימת, תעיר אותי" - או, תקרא לcallback שלי. ואיך מחכים שנייה בפייתון? from time import sleep sleep(1) print('Waked up') ואיך בJS? setTimeout(function() { alert('Waked up'); }, 1000); (ההבדל הוא כי בפייתון sleep מקבלת את הארגומנט שלה בשניות ובJS באלפיות השנייה). וRyan אמר, בNode שלי, יש רק thread אחד. אבל אם אתה רוצה לקרוא את הtemplate שלך, או להתחבר למסג נתונים, או לבקש את נתוני תחנת החלל הבינלאומית, אתה לא חוסם את הthread - אתה פשוט שם callback. וזה עובד נהדר. ובאמת, לחישובים כבדים Node.js לא כ"כ מתאימה (שוב, היום זה כבר השתנה), אבל לשרתים רגילים היא עובדת כמו קסם, ויעילה בהרבה מהשיטות המסורתיות. כל כך יותר יעילה, שכמעט כולם אימצו את הדרך שלה. אבל ישנה בעיה אחת, מהותית, לא קלה בכלל, עם callbacks - ויש לה אפילו שם: היא נקראת callback hell. וככה זה נראה, הגיהנום: app.post('/register', function(req, res) { const { username, password } = req.body; dbConnection.getByUserName(username, function(err, user) { if (err) { log(err); res.status(500 /* Internal server error */).end(); return; } if (user) { fs.readFile('../templates/register/user-already-exists.html', function(err, template) { if (err) { log(err); res.status(500).end(); return; } res.status(400 /* Bad request */).send(template); }); return; } bcrypt.genSalt(10, function(err, salt) { if (err) { log(err); res.status(500).end(); return; } bcrypt.hash(password, salt, function(err, passwordHash) { if (err) { log(err); res.status(500).end(); return; } dbConnection.addUser({ username, passwordHash }, function(err) { if (err) { log(err); res.status(500).end(); return; } fs.readFile('../templates/register/success.html', function(err, template) { if (err) { log(err); res.status(500).end(); return; } res.status(200 /* OK */).send(template); }); }); }); }); }); }); או לפי סימן ההיכר של הcallback hell: }); }); }); }); }); }); }); }); זה אמיתי. לא מעט קודים אכן נכתבו ככה. אתה בוודאי מבין שהבעיה היא חוסר הקריאות הנורא של הקוד. אבל זה יותר גרוע. קשה לעקוב אחרי הזרימה של הקוד. זו הסיבה העיקרית שאנחנו שונאים goto - הוא מבלבל את הקורא (וגם את הקומפיילר, אבל זה כבר סיפור אחר). קשה להבין לאן הולך כל קטע קוד. אם אבקש ממך להצביע לקטע הקוד שמטפל במקרה שהמשתמש כבר קיים ונכשלנו לקרוא את הHTML, כמה זמן יקח לך למצוא אותו? תשווה את זה לדבר הבא: app.post('/register', function(req, res) { const { username, password } = req.body; try { const user = dbConnection.getByUserNameSync(username); if (user) { const template = fs.readFileSync('../templates/register/user-already-exists.html'); res.status(400 /* Bad request */).send(template); return; } const salt = bcrypt.genSaltSync(10); const passwordHash = bcrypt.hashSync(password, salt); dbConnection.addUserSync({ username, passwordHash }); const template = fs.readFileSync('../templates/register/success.html'); res.status(200 /* OK */).send(template); } catch (err) { log(err); res.status(500).end(); } }); קצר יותר, קריא הרבה יותר, והכי חשוב: קל בהרבה לעקוב אחרי הזרימה של הקוד. למעשה, כששכתבתי את הגירסה למעלה לגירסה הזו מצאתי בה שגיאה. ונכון שאת אותה השגיאה הייתי מוצא גם בריצה הראשונה של הקוד (מה שלא היה קורה אם זה היה באחד מנתיבי השגיאה - כלומר קורה רק אם קרתה שגיאה), או אם הייתי משתמש בTypeScript, אבל זה רק מדגים עד כמה קל יותר להבין את העניין. אז את זה פתרו עם ...................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... Promises. מה, חשבת שאני אגיד async/await? הו, לא. לא כל כך מהר. אבל בכל זאת לשם הגענו בסוף, כי באמת promises, על אף שהיוו התקדמות משמעותית, לא היו מספיק. אז איך הקוד מלמעלה בגירסת async/await? app.post('/register', async function(req, res) { const { username, password } = req.body; try { const user = await dbConnection.getByUserName(username); if (user) { const template = await fs.promises.readFile('../templates/register/user-already-exists.html'); res.status(400 /* Bad request */).send(template); return; } const salt = await bcrypt.genSalt(10); const passwordHash = await bcrypt.hash(password, salt); await dbConnection.addUser({ username, passwordHash }); const template = await fs.promises.readFile('../templates/register/success.html'); res.status(200 /* OK */).send(template); } catch (err) { log(err); res.status(500).end(); } }); הרבה יותר דומה לגירסה הסינכרונית מאשר לגירסת הcallback, נכון? ותוכיח העובדה שהעתקתי מהגירסה הסינכרונית ולא מגירסת הcallbacks... בפוסט הבא: אז איך הקוד של async/await עובד, מה זה promises, מה זה TPL (ר"ת של Task Parallel Library) ואיך בכל זאת C# שונה מJS. המשך יבוא 1 ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
eido300 פורסם 2021 בינואר 6 מחבר Share פורסם 2021 בינואר 6 אואה תודה רבה. עם כאלו תשובות מפורטות ומושקעות כבר לא נעים לשאול... תודה רבה על כל ההשקעה. ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
af db creid פורסם 2021 בינואר 6 Share פורסם 2021 בינואר 6 (נערך) זה גם בשבילי, אני עושה את זה כשאני רוצה לקחת הפוגה נערך 2021 בינואר 6 על-ידי af db creid 1 ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
eido300 פורסם 2021 בינואר 17 מחבר Share פורסם 2021 בינואר 17 נו, עצרת בדיוק במתח, כבר שבוע לא יכול לישן. ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
eido300 פורסם 2021 בפברואר 4 מחבר Share פורסם 2021 בפברואר 4 אולי עכשיו הגיע הזמן? ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
eido300 פורסם 2021 בפברואר 8 מחבר Share פורסם 2021 בפברואר 8 טוב אני התייאשתי... ציטוט קישור לתוכן שתף באתרים אחרים More sharing options...
Recommended Posts
הצטרפ/י לדיון
בשלב זה תוכל/י להצטרף לדיון, ולאחר מכן להצטרף לקהילה שלנו. אם כבר יש לך חשבון אצלנו, אנא התחבר/י עכשיו על מנת להגיב תחת שם המשתמש שלך.
לתשומת לבך: התגובה תופיע לגולשים לאחר אישור של צוות הנהלת הפורומים.