שליטה בתהליך

תהליך בקרה הוא הסדר שבו רכיב התרגום של JavaScript מבצע הצהרות. אם סקריפט לא כולל הצהרות שמשנות את הזרימה שלו, מההתחלה ועד הסוף, שורה אחר שורה. מבני בקרה הם משמש כדי לקבוע אם קבוצה של הצהרות מבוצעת או לא על סמך קבוצת קריטריונים מוגדרת, לבצע קבוצה של הצהרות שוב ושוב, או להפריע רצף של הצהרות.

הצהרות מותנות קובעות אם צריך להריץ את הקוד על סמך תנאים נוספים. הצהרת תנאי מריצים את הקוד שהיא מכילה אם התנאי המשויך (או קבוצת תנאים) שווה ל-true. אחרת, הפרמטר המערכת מדלגת על הקוד.

if...else

ההצהרה if מעריכה תנאי בתוך הסוגריים התואמים אשר לעקוב. אם התנאי שבתוך הסוגריים מוערך ל-true, דף חשבון או דוח חסימה לאחר הסוגריים התואמים, מבוצעת הפעלה:

if ( true ) console.log( "True." );
> "True."

if ( true ) {
   
const myString = "True.";
    console
.log( myString );
}
> "True."

אם התנאי שבתוך הסוגריים שווה ל-false, המשפט לאחר שהמערכת מתעלמת ממנו:

if ( false ) console.log( "True." );

מילת מפתח מסוג else מיד אחרי משפט if הצהרה בנוגע לביצוע מותנה מציינת את ההצהרה שיש לבצע אם התנאי if מקבל ערך false:

if ( false ) console.log( "True." )''
else console.log( "False" );
> "False."

כדי לשרשר כמה הצהרות if ביחד, אפשר ליצור את הצהרה שבוצעה באופן מותנה אחרי else הצהרת if נוספת:

const myCondition = 2;
if ( myCondition === 5 ) console.log( "Five." );
else if ( myCondition === 2 ) console.log( "Two." );

מומלץ מאוד להשתמש בתחביר של הצהרת חסימה אחרי שהתנאים הבאים מתקיימים כדי משפרות את הקריאות, אבל במקרים רבים סעיפי else if הם יוצאים מן הכלל:

if ( myCondition === 5 ) {
    console
.log( "Five." );
} else if ( myCondition === 3 ) {
    console
.log( "Three" );
} else {
    console
.log( "Neither five nor three." );
}
> "Neither five nor three."

אופרטור תלת-ממדי

if יריץ הצהרה באופן מותנה. האופרטור המשולש (באופן מדויק יותר אבל פחות נפוץ אופרטור מותנה שלישי) הוא שימוש מקוצר. כדי להפעיל ביטוי באופן מותנה. כפי שמשתמע מהשם, הצבע המשולש הוא אופרטור ה-JavaScript היחיד שמשתמש בשלושה אופרנדים:

  • תנאי לבדיקה ואחריו סימן שאלה (?).
  • הביטוי שיופעל אם התנאי מקבל את הערך true, ואחריו נקודתיים (:).
  • הביטוי שיופעל אם התנאי מקבל את הערך false.

הפלח הזה משמש לעיתים קרובות כדי להגדיר או להעביר ערך באופן מותנה:

const myFirstResult  = true  ? "First value." : "Second value.";
const mySecondResult = false ? "First value." : "Second value.";

myFirstResult
;
> "First value."

mySecondResult
;
> "Second value."

switch...case

צריך להשתמש במשפט switch כדי להשוות ערך של ביטוי לרשימה של ערכים פוטנציאליים שהוגדרו באמצעות מילת מפתח אחת או יותר של case. התחביר הזה יוצא דופן, כי הוא מגיע מהחלטות העיצוב המוקדמות ביותר של JavaScript. switch...case משתמשים במילת המפתח switch ולאחר מכן ביטוי כדי בסוגריים בתוך סוגריים, ואחריהם זוג סוגריים מסולסלים תואמים. הגוף של switch יכול להכיל case מילות מפתח, בדרך כלל אחת או יותר, ואחריו ביטוי או ערך, ואחריו נקודתיים (:).

כשהמפענח מגיע ל-case עם ערך שתואם לביטוי מוערכת בסוגריים אחרי מילת המפתח switch, היא מבצעת כל הצהרות שעומדות בתנאי case הזה:

switch ( 2 + 2 === 4 ) {
 
case false:
    console
.log( "False." );
 
case true:
    console
.log( "True." );
}
> "True."

כל ההצהרות שאחרי case התואמות מתבצעות, גם אם בתוך הצהרת חסימה.

switch ( 2 + 2 === 4 ) {
   
case false:
    console
.log( "False." );
 
case true:
    let myVariable
= "True.";
    console
.log( myVariable );

}
> "True."

אחת מלכודות השימוש ב-switch…case היא שכאשר נמצאת התאמה, 'תרגום ב-JavaScript' מבצע הצהרה כלשהי שמופיעה אחרי case שהותאמה, גם בסעיפים אחרים של case. הפעולה הזו נקראת 'תהליך כשלים' ל case הבא:

switch ( 2 + 2 === 7 ) {
   
case false:
    console
.log( "False." );
 
case true:
    console
.log( "True." );
}
> "False."
> "True."

כדי למנוע נפילות, יש לסיים כל פנייה במילת המפתח break, המערכת מפסיקה מיד את ההערכה של הגוף switch:

switch ( 2 + 2 === 7 ) {
   
case false:
    console
.log( "False." );
   
break;
 
case true:
    console
.log( "True." );
   
break;
}
> "False."

אם אין case שתואם לערך המותנה, switch בוחר את הערך default אם יש תנאי כזה:

switch ( 20 ) {
   
case 5:
    console
.log( "The value was five." );
   
break;
 
case 10:
    console
.log( "The value was ten." );
   
break;
 
default:
    console
.log( "The value was something unexpected." );
}
> "The value was something unexpected."

עם זאת, המעבר האוטומטי חל גם על default, ועשוי להוביל מתוצאות בלתי צפויות. כדי לפתור את הבעיה, צריך לסיים את דף החשבון ב-default עם break, או להציב אותו אחרונה ברשימת המקרים.

switch ( 20 ) {
 
default:
    console
.log( "The value was something unexpected." );
 
case 10:
    console
.log( "The value was ten." );
   
break;
 
case 5:
    console
.log( "The value was five." );
   
break;
}
> The value was something unexpected.
> The value was ten.

כי בסעיפים של case לא נדרש חסימת משפט לקיבוץ מספר הצהרות, case ו-default לא יוצרים היקף מילוני כשלעצמו:

let myVariable;
switch ( true ) {
 
case true:
    let myVariable
= "True.";
   
break;
 
default:
    let myVariable
= "False.";
   
break;
}
> Uncaught SyntaxError: redeclaration of let myVariable

כדי לנהל את ההיקף, משתמשים בהצהרות חסימה:

let myVariable;
switch ( true ) {
 
case true: {
    let myVariable
= "True.";
   
break;
 
}
 
default: {
    let myVariable
= "False.";
   
break;
 
}
}

לולאות ואיטרציה

לולאות מאפשרות לחזור על קבוצה של הצהרות כל עוד מתקיים תנאי מסוים, או עד שתנאי מסוים מתקיים. להשתמש בלולאות כדי להפעיל קבוצה של הוראות מספר פעמים, עד לקבלת תוצאה ספציפית, או עד שהתרגום מגיע לסוף של מבנה נתונים שניתן לחזור עליו (לדוגמה, הרכיב הסופי מערך, מפה או קבוצה, את המאפיין הסופי של אובייקט, או את התו האחרון מחרוזת).

לולאות מפריעות לקטע 'מלמעלה למטה' של ביצוע סקריפט באמצעות חזרה מעל קבוצה של הצהרות עד שתנאי אחד או יותר יתקיימו או שאינם עוד בהתאם לתחביר ששימש ליצירת הלולאה. אחרי שהלולאה מסתיימת, נמשך לכל ההצהרות הבאות אחריו. בדוגמה הבאה, ההצהרות בגוף הלולאה מתבצעות שלוש פעמים לפני תרגום התוכן ממשיך:

let iterationCount = 0;
console
.log( "Pre-loop." );
while( iterationCount < 3 ) {
  iterationCount
++;
  console
.log( "Loop iteration." );
}
console
.log( "Continuing on." );
> "Pre-loop."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
> "Continuing on."

אם לא ניתן לעמוד בתנאים במהלך ביצוע הלולאה, היא תמשיך ללא הגבלת זמן. לולאות האינסופיות האלה הן מלכודת תכנות נפוצה שעלולה גורמים לשרשור הביצוע הראשי כדי להשהות ללא הגבלת זמן או אפילו לקרוס כרטיסייה בדפדפן.

הדוגמה הבאה תפעל כל עוד הערך הבוליאני true נותר true מכיוון שערכים בוליאניים אינם ניתנים לשינוי, נוצרת לולאה אינסופית.

console.log( "Pre-loop." );
while( true ) {
console
.log( "Loop iteration." );
}
> "Pre-loop."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."
> "Loop iteration."

יש להימנע מהשארת לולאות אינסופיות בקוד הייצור. אם יצרתם בטעות במהלך הפיתוח, ניתן לתקן את הבעיה על ידי סגירת כרטיסיית הדפדפן שבה הוא פועל ולעדכן את הקוד כך שהלולאה לא תהיה אינסופית יותר, ופתיחה מחדש של הדף הזה.

while

לולאה של while נוצרת באמצעות מילת המפתח while ולאחר מכן צמד סוגריים תואמים שמכילים תנאי לבדיקה. אם הערך הראשוני של התנאי הוא true, את ההצהרה (או חסימת דוח) שמופיע אחרי הסוגריים האלה מופעלים. אם לא, הלולאה אף פעם לא תפעל. אחרי כל איטרציה, התנאי ייבדק מחדש ואם הוא עדיין true, הלולאה וחוזרים שלו.

let iterationCount = 0;
while( iterationCount < 3 ) {
  iterationCount
++;
  console
.log( `Loop ${ iterationCount }.` );
}
> "Loop 1."
> "Loop 2."

אם המתרגם מוצא הצהרה continue בלולאה של while, היא מפסיקה איטרציה, מעריך מחדש את התנאי וממשיכה את הלולאה אם אפשר:

let iterationCount = 0;
while( iterationCount <= 5 ) {
  iterationCount
++;
 
if( iterationCount === 3 ) {
   
continue;
 
}
  console
.log( `Loop ${ iterationCount }.` );
}
console
.log( "Loop ended." );
> "Loop 1."
> "Loop 2."
> "Loop 4."
> "Loop 5."
> "Loop ended."

אם המתרגם מוצא את הצהרת break בלולאת while, האיטרציה הזו נפסקת והתנאי לא נבדק מחדש, וכך מאפשר למפענח להמשיך להתקדם:

let iterationCount = 1;
while( iterationCount <= 5 ) {
 
if( iterationCount === 3 ) {
    console
.log(`Iteration skipped.``);`
   
break;
 
}
  console
.log( `Loop ${ iterationCount }.` );
  iterationCount
++;
}
console
.log( "Loop ended." );
> "Loop 1."
> "Loop 2."
> "Iteration skipped.
> "
Loop ended."

אפשר להשתמש בפונקציה while כדי לחזור מספר מסוים של פעמים, כפי שניתן לראות ב אבל התרחיש לדוגמה הנפוץ ביותר של while הוא לולאה של אורך קבוע:

let randomize = () => Math.floor( Math.random() * 10 );
let randomNum
= randomize();
while( randomNum !== 3 ){
  console
.log( `The number is not ${ randomNum }.` );
  randomNum
= randomize();
}
console
.log( `The correct number, ${ randomNum }, was found.` );
> "The number is not 0."
> "The number is not 6."
> "The number is not 1."
> "The number is not 8."
> "The correct number, 3, was found."

do...while

do...while הוא וריאנט של לולאת while שבה התנאי המותנה ההערכה מתבצעת בסוף כל חזרה של הלולאה. המשמעות היא גוף הלולאה תמיד מתבצע לפחות פעם אחת.

כדי ליצור לולאה של do...while, משתמשים במילת המפתח do ולאחר מכן במשפט (או דוח חסימה) להיות בכל חזרה של הלולאה. מיד לאחר ההצהרה, מוסיפים while וסוגריים תואמים שמכילים את התנאי שצריך לבדוק. מתי התנאי הזה כבר לא מקבל ערך true, והלולאה מסתיימת.

let iterationCount = 1;
do {
  console
.log( `Loop ${ iterationCount }.` );
  iterationCount
++;
} while ( iterationCount < 3 );
> "Loop 1."
> "Loop 2."
> "Loop 3."

כמו לולאת while, התרחיש לדוגמה הנפוץ ביותר של do...while הוא לולאה של אורך קבוע:

let randomNum;
do {
  randomNum
= ( () => Math.floor( Math.random() * 10 ) )();
  console
.log( `Is the number ${ randomNum }?` );
} while ( randomNum !== 3 );
console
.log( `Yes, ${ randomNum } was the correct number.` );
> "Is the number 9?"
> "Is the number 2?"
> "Is the number 8?"
> "Is the number 2?"
> "Is the number 3?"
> "Yes, 3 was the correct number."

for

משתמשים בלולאות של for כדי לחזור על כמות ידועה. בבסיסי קוד מדור קודם, הפעולה הזו הייתה שמשמשות לעיתים קרובות כדי לבצע איטרציה לרכיבים במערך.

כדי ליצור לולאת for, משתמשים במילת המפתח for ולאחר מכן בקבוצה של סוגריים מקבלת את שלושת הביטויים הבאים לפי הסדר ומופרדים באמצעות נקודה ופסיק:

  1. ביטוי שצריך להעריך כשהלולאה מתחילה
  2. תנאי שקובע אם הלולאה תמשיך
  3. ביטוי שצריך לבצע בסיומה של כל לולאה

אחרי הסוגריים האלו, מוסיפים את ההצהרה (בדרך כלל חסימת דוח) להיות שבוצעו במהלך הלולאה.

for( let i = 0; i < 3; i++ ) {
  console
.log( "This loop will run three times.")
}

הביטוי הראשון מאתחל משתנה שמשמש כמונה. הזה ביטוי מוערך פעם אחת, לפני האיטרציה הראשונה של הלולאה. אפשר צריך לאתחל את המשתנה הזה באמצעות let (או var, מבחינה היסטורית) כמו כל משתנה אחר וההיקף שלו הוא גוף הלולאה. המשתנים האלה יכולים לכלול כל אבל הם מכונים לעיתים קרובות i ל"איטרציה" או 'index'. נראה שסותרת לכך שיטות מומלצות לשמות של מזהים שניתן לחזות, אבל המוסכמה מבוססת מספיק כדי להיות ברורה למפתחים אחרים בקצרה. מכיוון שאוספים שנוספו לאינדקס לא נוספו לאינדקס, למשתנים האלה יש כמעט תמיד הערך הראשוני של 0.

בדומה לצורות אחרות של לולאה, התנאי הוא ביטוי שקובע האם לבצע את הלולאה. בדרך כלל משתמשים באפשרות הזו כדי להגדיר למונה האיטרציה. המתורגמן מעריך את התנאי לפני הרצת הלולאה for בפעם הראשונה.אם התנאי לא פועל בהתחלה ולהעריך את הערך true, גוף הלולאה לא מתבצע.

הביטוי הסופי מופעל בסוף כל איטרציה דרך הלולאה. בדרך כלל משתמשים בו כדי להגדיל את המזהה באחד מהם.

בדרך כלל יופיעו לולאות של for שחוזרות על עצמן בין מערכים בגרסאות ישנות יותר ב-codebases. במקרים כאלה, התנאי שצוין להמשך הלולאה הוא מספר האיטרציה קטן או שווה לאורך המערך שרוצים להסיר באמצעות. המשתנה שמשמש למעקב אחרי ספירת האיטרציה הנוכחית משמש כדי לבדוק את הערך שמשויך לאינדקס הזה במערך, וכך מאפשר לכל רכיב המערך שיש לבצע בו פעולות לפי הסדר:

var myArray = [ true, false, true ];
for( let i = 0; i <= myArray.length; i++ ) {
  console
.log( myArray[ i ] );
}
> true
> false
> true

הגישה הזו יצאה משימוש לטובת גישות מודרניות יותר חזרה בלופ דרך מבני נתונים שניתנים לאיטרציה.

for [...] of [...]

שימוש בלולאות for...of... כדי לחזור על הערכים שמאוחסנים מבנה נתונים שאפשר לשנות, כמו מערך, קבוצה או מפה.

לולאת for...of... משתמשת במילת המפתח for ואחריה קבוצת סוגריים שמכיל משתנה, ואחריו of, ואז מבנה הנתונים שעובר איטרציה מעל. המשתנה יכול להיות הצהרה שמבוצעת כאן באמצעות let, const, או var, משתנה שהוצהר בעבר במסגרת ההיקף הנוכחי, אובייקט או מופע של מסירים את המטלה. היא מכילה את הערך של הרכיב שתואם לאיטרציה הנוכחית של הלולאה.

const myIterable = [ true, false, true ];
for( const myElement of myIterable ) {
  console
.log( myElement );
}
> true
> false
> true

בדוגמה הזו, השימוש ב-const עבור myElement עובד למרות שהפונקציה myElement מקבלים ערך חדש בכל איטרציה של הלולאה. הסיבה לכך היא שמשתנים שהוצהרו באמצעות let או const בהיקף של הצהרת החסימה בתוך לולאה. המשתנה מאותחל בתחילת כל איטרציה ומוסר ב- סוף האיטרציה.

for...in...

משתמשים בלולאות for...in... כדי לבצע איטרציה למאפיינים שניתנים לספירה של אובייקט, כולל נכסים שעברו בירושה לספירה. כמו בלולאת for...of..., לולאת for...in... משתמשת במילת המפתח for ואחריה קבוצת סוגריים שמכיל משתנה שמכיל את הערך של מפתח המאפיין, באיטרציה הנוכחית של הלולאה. אחרי המשתנה הזה מופיע מילת מפתח אחת (in), ואז האובייקט שעובר באיטרציה:

const myObject = { "myProperty" : true, "mySecondProperty" : false };
for( const myKey in myObject ) {
  console
.log( myKey );
}
> "myProperty"
> "mySecondProperty"

שוב, למרות שהערך של myKey משתנה בכל חזרה של הלולאה, אפשר להשתמש בפונקציה const ללא שגיאה כי המשתנה נמחק בפועל בסוף כל איטרציה, ואז נוצר מחדש בהתחלה.

הערך שמשויך לכל מפתח נכס לא זמין ישירות תחביר for...in.... עם זאת, מכיוון שללולאה יש גישה למפתח מאפיין בכל חזרה, תוכלו להשתמש במקש הזה כדי "לחפש" הערך שלו:

const myObject = { "myProperty" : true, "mySecondProperty" : false };
for( const myKey in myObject ) {
 
const myValue = myObject[ myKey ];
  console
.log( `${ myKey } : ${ myValue }` );
}
> "myProperty : true"
> "mySecondProperty : false"

מאפיינים שעברו בירושה מבנאים מובנים לא ניתנים לספירה, כלומר for...in... לא מתבצע איטרציה באמצעות נכסים שעברו בירושה מה-Object constructor. עם זאת, כל מאפיין שניתן לספירה בתוך האובייקט שרשרת אב הטיפוס כלולה:

const myPrototype = { "protoProperty" : true };
const myObject = Object.create( myPrototype, {
    myProperty
: {
    value
: true,
    enumerable
: true
   
}
});
for ( const myKey in myObject ) {
 
const myValue = myObject[ myKey ];
  console
.log( `${ myKey } : ${ myValue }` );
}
> "myProperty : true"
> "protoProperty : true"

JavaScript מספק שיטות מובנות כדי לקבוע אם נכס הוא נכס מאפיין ישיר של האובייקט ולא מאפיין באב הטיפוס של האובייקט שרשרת: מודרנית Object.hasOwn() ו-Object.prototype.hasOwnProperty() methods. האלה methods בודקות אם נכס שצוין עובר בירושה (או לא מוצהר), החזרת true רק למאפיינים המיידיים של אובייקט שצוין:

const myPrototype = { "protoProperty" : true };
const myObject = Object.create( myPrototype, {
    myProperty
: {
    value
: true,
    enumerable
: true
   
}
});
for ( const myKey in myObject ) {
 
const myValue = myObject[ myKey ];
 
if ( Object.hasOwn( myObject, myKey ) ) {
    console
.log( `${ myKey } : ${ myValue }` );
 
}
}
> "myProperty : true"

יש גם שלוש שיטות סטטיות שכל אחת מהן מחזירה מערך, שמורכב מפתחות המספור של האובייקט (Object.keys()), ערכים (Object.values()) או צמדי מפתח/ערך (Object.entries()):

const myObject = { "myProperty" : true, "mySecondProperty" : false };
Object.keys( myObject );
> Array [ "myProperty", "mySecondProperty" ]

כך אפשר לחזור על מפתחות, ערכים או צמדי מפתח/ערך של אובייקטים (באמצעות השמטת מטלה) בלי לכלול מאפיינים שבבעלות אב הטיפוס של האובייקט:

const myPrototype = { "protoProperty" : "Non-enumerable property value." };
const myObject = Object.create( myPrototype, {
    myProperty
: {
    value
: "Enumerable property value.",
    enumerable
: true
   
}
});

for ( const propKey of Object.keys( myObject ) ) {
  console
.log( propKey );
}
> "myProperty"

for ( const propValue of Object.values( myObject ) ) {
  console
.log( propValue );
}
> "Enumerable property value."

for ( const [ propKey, propValue ] of Object.entries( myObject ) ) {
  console
.log( `${ propKey } : ${ propValue }` );
}
> "myProperty : Enumerable property value."

forEach()

ה-methods forEach() שמסופקות על ידי המערך, מפה, הגדרה, ו-NodeList constructors מספקים קיצור דרך שימושי לאיטרציה על נתונים בהקשר של פונקציית קריאה חוזרת. בניגוד לצורות אחרות של לולאה, אי אפשר להפסיק לולאה שנוצרה בכל method של forEach() באמצעות break או continue.

forEach היא שיטה בבעלות אב הטיפוס של כל מבנה נתונים. בכל forEach מצפה לפונקציית קריאה חוזרת כארגומנט, אם כי הם שונים מעט של הארגומנטים שמופיעים כשקוראים לפונקציה הזו. אפשרות שנייה, לא חובה הארגומנט מציין ערך this שישמש בתור ההקשר של ההפעלה עבור של פונקציית קריאה חוזרת.

פונקציית הקריאה החוזרת שבה נעשה שימוש עם Array.forEach מספקת פרמטרים שכוללים הערך של הרכיב הנוכחי, האינדקס של הרכיב הנוכחי והמערך שבו הופעלה השיטה forEach:

const myArray = [ true, false ];
myArray
.forEach( ( myElement, i, originalArray ) => {
  console
.log( i, myElement, originalArray  );
});
> 0 true Array(3) [ true, false ]
> 1 false Array(3) [ true, false ]

פונקציית הקריאה החוזרת שבה נעשה שימוש עם Map.forEach מספקת פרמטרים שכוללים שמשויך לאלמנט הנוכחי, המפתח שמשויך לרכיב הנוכחי והשיטה 'מפה' הופעלה ב:forEach

const myMap = new Map([
 
['myKey', true],
 
['mySecondKey', false ],
]);
myMap
.forEach( ( myValue, myKey, originalMap ) => {
    console
.log( myValue, myKey, originalMap  );
});
> true "myKey" Map { myKey true, mySecondKey false }
> false "mySecondKey" Map { myKey true, mySecondKey false }

הקריאה החוזרת (callback) של Set.forEach כוללת פרמטרים דומים. מפני שבהגדרה אין או מפתחות נפרדים מערכים, במקום זאת הארגומנט השני מספק ערך מיותר שאפשר להתעלם ממנו, במטרה לשמור על תחביר עקבי שיטות forEach אחרות.

const mySet = new Set([ true, false ]);
mySet
.forEach( ( myValue, myKey, originalSet ) => {
  console
.log( myValue, myKey, originalSet  );
});
> true true Set [ true, false ]
> false false Set [ true, false ]

איטרטורים

נתונים שאפשר לבצע עליהם איטרציה הם מבנה נתונים שמורכב מרכיבים נפרדים בחזרה על שימוש בגישות שפירטנו קודם לכן. איטרטור הוא אובייקט שניתן לחזור עליו אחרי פרוטוקול איטרטור, כלומר עליו להטמיע רכיב next() שמקדם את הרכיבים שהוא מכיל, אחד בכל פעם, בכל קריאה ל-method הזה, החזרת אובייקט עבור כל רצף בפורמט מסוים.

מבני הנתונים המובנים של JavaScript שניתנים לחזרה (כמו Array, מפה, וגם Set) אינן של איטרטורים בעצמם, אבל כולם יורשים את שיטת iterator שניתן לגשת אליה באמצעות @@iterator סמל ידוע, שמחזיר אובייקט איטרטור שנוצר ממבנה הנתונים האיטרטיבי:

const myIterable = [ 1, 2, 3 ];
const myIterator = myIterable[ Symbol.iterator ]();

myIterable
;
> (3) [1, 2, 3]

myIterator
;
> Array Iterator {}

קריאה לשיטה next() באיטרטור צעדים דרך הרכיבים שהיא מכיל מילה אחת בכל פעם, וכל קריאה מחזירה אובייקט שמכיל שני המאפיינים: value, שמכיל את הערך של הרכיב הנוכחי, ו- done, ערך בוליאני שאומר לנו אם האיטרטור עבר את הרכיב האחרון ב- את מבנה הנתונים. הערך של done הוא true רק כאשר שיחה ל-next() שינסה לגשת לרכיב מעבר לרכיב האחרון באיטרטור.

const myIterable = [ 1, 2, 3 ];
const myIterator = myIterable[ Symbol.iterator ]();

myIterator
.next();
> Object { value: 1, done: false }

myIterator
.next();
> Object { value: 2, done: false }

myIterator
.next();
> Object { value: 3, done: false }

myIterator
.next();
> Object { value: undefined, done: true }

פונקציות של גנרטור

כדי להצהיר על מחולל, צריך להשתמש במילת המפתח function* (שימו לב לכוכבית) או להגדיר ביטוי של פונקציית מחולל:

function* myGeneratorFunction() { };

בדומה לאיטרטורים, פונקציות הגנרטור שומרות על המצב. התקשרות אל פונקציית הגנרטור מחזירה אובייקט Generator חדש, אבל לא שולחת אותה באופן מיידי מריצים את הקוד בגוף הפונקציה:

function* myGeneratorFunction() {
  console
.log( "Generator function body ")
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject
;
> Generator {  }

typeof myGeneratorObject;
> "object"

אובייקטים של גנרטור פועלים לפי פרוטוקול איטרטור. הערך של כל קריאה הפונקציה next() שמחזירה פונקציית מחולל נקבעת באמצעות ביטוי yield, שמשהה את הביצוע של פונקציית המחולל ומחזירה את הערך שמכיל את מילת המפתח yield. שיחות מאוחרות יותר אל next() ממשיכים לבצע את הפונקציה, ומשהים בביטוי yield הבא שמחזירה את הערך המשויך.

function* myGeneratorFunction() {
 
yield "My first yielded value.";
 
yield "My second yielded value.";
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject
.next();
> Object { value: "My first yielded value.", done: false }

myGeneratorObject
.next();
> Object { value: "My second yielded value.", done: false }

כשמתבצעת קריאה אל next() אחרי שלא צוינו ערכים נוספים באמצעות yield, return, או throw (במקרה של שגיאה), שאר הפונקציה בגוף מסוים, ובאובייקט שהוחזר יש value של undefined ו-done נכס של true:


function* myGeneratorFunction() {
    console
.log( "Start of the generator function." );
   
yield "First";
    console
.log( "Second part of the generator function."  );
   
yield "Second";
    console
.log( "Third part of the generator function." );
   
yield "Third";
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject
.next();
> "Start of the generator function."
> Object { value: "First", done: false }

myGeneratorObject
.next();
> "Second part of the generator function."
> Object { value: "Second", done: false }

myGeneratorObject
.next();
> "Third part of the generator function."
> Object { value: "Third", done: false }

myGeneratorObject
.next();
> Object { value: undefined, done: true }

משתמשים ב-next() רק באובייקט שמחזירה פונקציית המחולל, ולא ב במחולל עצמו. אחרת, כל קריאה לפונקציית המחולל יוצרת אובייקט יוצר חדש:

function* myGeneratorFunction() {
 
yield "First";
 
yield "Second";
};

myGeneratorFunction
().next();
> Object { value: "First", done: false }

myGeneratorFunction
().next();
> Object { value: "First", done: false }

כמו בכל פונקציה, פונקציית המחולל נעצרת כשהיא נתקלת ב-return במילת מפתח. לאחר מכן היא מחזירה אובייקט להֶקשר שמפעיל את האובייקט שמכיל את מוחזר ערך ומאפיין done עם הערך true.

function* myGeneratorFunction() {
 
yield 1;
 
yield 2;
 
return 3;
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject
.next().done;
> Object { value: 1, done: false }

myGeneratorObject
.next().done;
> Object { value: 2, done: false }

myGeneratorObject
.next();
> Object { value: 3, done: true }

ביטוי yield יכול לקבל חלק מהסמנטיקה של מזהה, לאפשר "תקשורת" דו-כיוונית ובחזרה אל החלק המושעה של המחולל. כשערך מועבר ל-method next() של מחולל כ- ארגומנט, הוא מחליף את הערך שמשויך לערך הקודם, שהושעה ביטוי yield:

function* myGeneratorFunction() {
   
const firstYield = yield;
   
yield firstYield + 10;
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject
.next();
> Object { value: undefined, done: false }

myGeneratorObject
.next( 5 );
> Object { value: 15, done: false }

חשוב לזכור שהביטוי הזה מחליף את כל הביטוי שמשויך את yield הקודם, ולא רק מקצה מחדש את הערך של yield הקודם ל- הערך שצוין ב-next():

function* myGeneratorFunction() {
   
const firstYield = yield;
   
const secondYield = yield firstYield + 100;
   
yield secondYield + 10;
};
const myGeneratorObject = myGeneratorFunction();

myGeneratorObject
.next();
> Object { value: undefined, done: false }

myGeneratorObject
.next( 10 ); // Can be thought of as changing the value of the `firstYield` variable to `10
> Object { value: 110, done: false }

myGeneratorObject
.next( 20 ); // Can be thought of as changing the value of the `secondYield` variable to `20`, _not_ `20 + 100;`
> Object { value: 30, done: false }

המערכת מתעלמת מכל ארגומנט שמועבר לקריאה הראשונה אל next(), כי לא לביטוי yield הקודם כדי לקבל את הערך הזה. כמו בכל פונקציה אחרת, הארגומנטים שהועברו להפעלה הראשונית של פונקציית המחולל זמינים בכל היקף הגוף של פונקציית הגנרטור:

function* myGeneratorFunction( startingValue ) {
    let newValue
= yield startingValue + 1;
    newValue
= yield newValue + 10;
   
yield startingValue + 20;
};
const myGeneratorObject = myGeneratorFunction( 2 );

myGeneratorObject
.next( 1 );
> Object { value: 3, done: false }

myGeneratorObject
.next( 5 );
> Object { value: 15, done: false }

myGeneratorObject
.next( 10 );
Object { value: 22, done: false }

האופרטור yield* (שים לב לכוכבית) משתמשים באופרטור חזרתי, כמו פונקציית גנרטור אחרת, שנועדה לבצע איטרציה ולהניב כל ערך האופרנד שלו החזרות:

function* mySecondaryGenerator() {
 
yield 2;
 
yield 3;
}

function* myGenerator() {
 
yield 1;
 
yield* mySecondaryGenerator();
 
yield 4;
 
return 5;
}

const myIterator = myGenerator();

myIterator
.next();
> Object { value: 1, done: false }

myIterator
.next();
> Object { value: 2, done: false }

myIterator
.next();
> Object { value: 3, done: false }

myIterator
.next();
> Object { value: 4, done: false }

myIterator
.next();
> Object { value: 5, done: true }

JavaScript אסינכרוני

למרות ש-JavaScript הוא סינכרוני במהותו יש מנגנונים שמאפשרים למפתחים לנצל לולאת האירוע לביצוע ומשימות אסינכרוניות.

הבטחות

הבטחה היא placeholder של ערך שאינו ידוע במועד ההבטחה נוצר. זהו קונטיינר שמכתיב פעולה אסינכרונית, התנאים שהפעולה נחשבת כהצלחה או כנכשלה, אלה הפעולות שצריך לבצע. בכל מקרה, וגם את הערך שיתקבל.

יוצרים מכונה של Promise באמצעות האופרטור new עם Promise המובנה של constructor. ה-constructor הזה מקבל פונקציה שנקראת executor כארגומנט. פונקציית הביצוע בדרך כלל משמשת לביצוע פעולה אחת או יותר פעולות אסינכרוניות, ואז מכתיבים את המונחים שלפיהם ההבטחה צריכה להיות נחשב כמומש או נדחה. הבטחה מוגדרת כ'בהמתנה' בזמן שפונקציית המפעילים פועלת. לאחר שהמוציא לפועל מסיים, הבטחה נחשב כפוף (או נפתרה, בחלק ממקורות המסמכים) אם פונקציית הביצוע והפעולה האסינכרונית שהיא מבצעת הושלמה בהצלחה, ונדחה אם פונקציית הביצוע נתקלת בשגיאה, או הפעולה האסינכרונית שמבוצעת נכשלת. לאחר שהבטחה מוגנת או הוא נחשב הוסדר.

const myPromise = new Promise( () => { });

ה-constructor קורא לפונקציית הביצוע עם שני ארגומנטים. הארגומנטים האלה הן פונקציות שמאפשרות לבצע או לדחות את ההבטחה באופן ידני:

const  myPromise = new Promise( ( fulfill, reject ) => { });

לפונקציות המשמשות למילוי או לדחייה של ההבטחה נקראות גם התוצאות של ההבטחה כארגומנט (בדרך כלל שגיאה לדחייה):

const myPromise = new Promise( ( fulfill, reject ) => {
 
const myResult = true;
  setTimeout
(() => {
   
if( myResult === true ) {
        fulfill
( "This Promise was successful." );    
   
} else {
        reject
( new Error( "This Promise has been rejected." ) );
   
}
 
}, 10000);
});

myPromise
;
> Promise { <state>: "pending" }

myPromise
;
> Promise { <state>: "fulfilled", <value>: "This Promise was successful." }

קישור בין התחייבויות

אפשר לפעול על אובייקט ה-Promise שנוצר באמצעות then(), catch() finally() methods עברו בירושה מה-Promise constructor. כל אחד מאלה methods מחזירה הבטחה, שאפשר לבצע עליה פעולה באופן מיידי באמצעות then(), catch(), או finally() שוב, וכך לשרשר את ההבטחות שהתקבלו.

הפונקציה then() מספקת שתי פונקציות קריאה חוזרת כארגומנטים. משתמשים בראשון לאספקה את ההבטחה שהתקבלה, והשנייה לדחות אותה. שתי שיטות התשלום מקבלות קוד אימות אחד המעניק את הערך של ההבטחה שמתקבלת.

const myPromise = new Promise( ( fulfill, reject ) => {
 
const myResult = true;
  setTimeout
(() => {
   
if( myResult === true ) {
        fulfill
( "This Promise was fulfilled." );    
   
} else {
        reject
( new Error( "This Promise has been rejected." ) );
   
}
 
}, 100);
});

myPromise
.then( successfulResult => console.log( successfulResult ), failedResult => console.error( failedResult ) );
> "This Promise was successful."

אפשר גם להשתמש ב-then() כדי לטפל רק במצב שסופק, וב-catch כדי לטפל תטפל במצב הדחייה. קוראים לפונקציה catch עם ארגומנט יחיד שמכיל את הפונקציה הערך שצוין בשיטת הדחייה של Promise:

const myPromise = new Promise( ( fulfill, reject ) => {
 
const myResult = false;
  setTimeout
(() => {
   
if( myResult === true ) {
        fulfill
( "This Promise was fulfilled." );    
   
} else {
        reject
( new Error( "This Promise has been rejected." ) );
   
}
 
}, 100);
});

myPromise
 
.then( fulfilledResult => console.log(fulfilledResult ) )
 
.catch( rejectedResult => console.log( rejectedResult ) )
 
.finally( () => console.log( "The Promise has settled." ) );
> "Error: This Promise has been rejected."
> "The Promise has settled."

בניגוד ל-then ול-catch, שמאפשרים לפונקציית handler לרוץ כשיש הבטחה מתבצע או נדחה, פונקציה שמועברת כארגומנט אל finally נקראת בין שההבטחה מומשה או נדחתה. הפונקציה של handler קריאה ללא ארגומנטים, כי היא לא מיועדת לעבוד עם הערכים המועברים מה-Promise, ולאחר מכן להריץ את הקוד ההבטחה בוצעה.

בו-זמניות

Promise constructor מספק ארבע שיטות לעבודה עם מספר הבטחה, באמצעות שדה חוזר שמכיל אובייקטים של Promise. האלה שכל אחת מהן מחזירה הבטחה, שמתממשת או נדחתה בהתאם למדינה מההבטחות שהועברו אליו. לדוגמה, Promise.all(), יוצר הבטחה שמתקיימים רק אם כל ההבטחה שמועברת לשיטה הזו מתקיים:

const firstPromise  = new Promise( ( fulfill, reject ) => fulfill( "Successful. ") );
const secondPromise = new Promise( ( fulfill, reject ) => fulfill( "Successful. ") );
const thirdPromise  = new Promise( ( fulfill, reject ) => fulfill( "Successful. ") );
const failedPromise = new Promise( ( fulfill, reject ) => reject( "Failed.") );
const successfulPromises = [ firstPromise, secondPromise, thirdPromise ];
const oneFailedPromise = [ failedPromise, ...successfulPromises ];

Promise.all( successfulPromises )
 
.then( ( allValues ) => {
    console
.log( allValues );
 
})
 
.catch( ( failValue ) => {
    console
.error( failValue );
 
});
> Array(3) [ "Successful. ", "Successful. ", "Successful. " ]

Promise.all( oneFailedPromise  )
   
.then( ( allValues ) => {
      console
.log( allValues );
   
})
   
.catch( ( failValue ) => {
     console
.error( failValue );
   
});
> "Failed."

אלה השיטות של Promise בו-זמניות (concurrency):

Promise.all()
ממומש רק אם כל ההבטחות שסופקו יתקיימו.
Promise.any()
מימוש אם אחת מההבטחות שסופקו מתקיים ונדחה בלבד אם כל ההבטחות נדחו.
Promise.allSettled()
ממומשת לאחר שההבטחות יושבות, ללא קשר לתוצאה שלהן.
Promise.race()
נדחה או מומש על סמך תוצאת ההבטחה הראשונה להסדר, והתעלמו מכל ההבטחות שהוסדרו מאוחר יותר.

async/await

כשמשתמשים במילת המפתח async לפני הצהרה על פונקציה או בביטוי פונקציה, כל שהפונקציה מחזירה עם ערך מסוים. כך אפשר להריץ ולנהל פעולות אסינכרוניות באמצעות תהליכי עבודה כפיתוח סינכרוני.

async function myFunction() {
 
return "This is my returned value.";
}

myFunction
().then( myReturnedValue => console.log( myReturnedValue ) );
> "This is my returned value."

הביטוי await משהה את הביצוע של פונקציה אסינכרונית בזמן ההבטחה המשויכת מוסדרת. לאחר יישוב ההבטחה, הערך של הביטוי await הוא הערך של ההבטחה שמולא או נדחה.

async function myFunction() {
 
const myPromise  = new Promise( ( fulfill, reject ) => { setTimeout( () => fulfill( "Successful. "), 5000 ); });
 
const myPromisedResult = await myPromise;
 
return myPromisedResult;
}

myFunction
()
 
.then( myResult => console.log( myResult ) )
 
.catch( myFailedResult => console.error( myFailedResult ) );
> "Successful."

כל ערך שהוא לא אובייקטיבי שנכלל בביטוי await מוחזר בתור הבטחה שמולאה:

async function myFunction() {
 
const myPromisedResult = await "String value.";
 
return myPromisedResult;
}

myFunction
()
 
.then( myResult => console.log( myResult ) )
 
.catch( myFailedResult => console.error( myFailedResult ) );
> "String value."

בדיקת ההבנה

באיזה סוג לולאה אתם משתמשים כדי לחזור על כמות ידועה?

for
while
do...while