This chapter covers the following control flow statements:
if statement [ES1]
switch statement [ES3]
while loop [ES1]
do-while loop [ES3]
for loop [ES1]
for-of loop [ES6]
for-await-of loop [ES2018]
for-in loop [ES1]
break and continueThe two operators break and continue can be used to control loops and other statements while we are inside them.
break
There are two versions of break:
The former version works inside the following statements: while, do-while, for, for-of, for-await-of, for-in and switch. It immediately leaves the current statement:
for (const x of ['a', 'b', 'c']) {
console.log(x);
if (x === 'b') break;
console.log('---')
}
Output:
a---b
break plus label: leaving any labeled statement
break with an operand works everywhere. Its operand is a label. Labels can be put in front of any statement, including blocks. break myLabel leaves the statement whose label is myLabel:
myLabel: { // label
if (condition) break myLabel; // labeled break
// ···
}
break plus labelIn the following example, the search can either:
result. That is handled directly after the loop (line B).
result. Then we use break plus label (line A) to skip the code that handles failure.
function findSuffix(stringArray, suffix) {
let result;
searchBlock: {
for (const str of stringArray) {
if (str.endsWith(suffix)) {
// Success:
result = str;
break searchBlock; // (A)
}
} // for
// Failure:
result = '(Untitled)'; // (B)
} // searchBlock
return { suffix, result };
// Same as: {suffix: suffix, result: result}
}
assert.deepEqual(
findSuffix(['notes.txt', 'index.html'], '.html'),
{ suffix: '.html', result: 'index.html' }
);
assert.deepEqual(
findSuffix(['notes.txt', 'index.html'], '.mjs'),
{ suffix: '.mjs', result: '(Untitled)' }
);
continue
continue only works inside while, do-while, for, for-of, for-await-of, and for-in. It immediately leaves the current loop iteration and continues with the next one – for example:
const lines = [
'Normal line',
'# Comment',
'Another normal line',
];
for (const line of lines) {
if (line.startsWith('#')) continue;
console.log(line);
}
Output:
Normal lineAnother normal line
if, while, and do-while have conditions that are, in principle, boolean. However, a condition only has to be truthy (true if coerced to boolean) in order to be accepted. In other words, the following two control flow statements are equivalent:
if (value) {}
if (Boolean(value) === true) {}
This is a list of all falsy values:
undefined, null
false
0, NaN
0n
''
All other values are truthy. For more information, see “Falsy and truthy values” (§17.2).
if statements ES1
These are two simple if statements: one with just a “then” branch and one with both a “then” branch and an “else” branch:
if (cond) {
// then branch
}
if (cond) {
// then branch
} else {
// else branch
}
Instead of the block, else can also be followed by another if statement:
if (cond1) {
// ···
} else if (cond2) {
// ···
}
if (cond1) {
// ···
} else if (cond2) {
// ···
} else {
// ···
}
We can continue this chain with more else ifs.
if statementsThe general syntax of if statements is:
if («cond») «then_statement»
else «else_statement»
So far, the then_statement has always been a block, but we can use any statement. That statement must be terminated with a semicolon:
if (true) console.log('Yes'); else console.log('No');
That means that else if is not its own construct; it’s simply an if statement whose else_statement is another if statement.
switch statements ES3
A switch statement looks as follows:
switch («switch_expression») {«switch_body»}
The body of switch consists of zero or more case clauses:
case «case_expression»:«statements»
And, optionally, a default clause:
default:«statements»
A switch is executed as follows:
switch statementLet’s look at an example: The following function converts a number from 1–7 to the name of a weekday.
function dayOfTheWeek(num) {
switch (num) {
case 1:
return 'Monday';
case 2:
return 'Tuesday';
case 3:
return 'Wednesday';
case 4:
return 'Thursday';
case 5:
return 'Friday';
case 6:
return 'Saturday';
case 7:
return 'Sunday';
}
}
assert.equal(dayOfTheWeek(5), 'Friday');
return or break!At the end of a case clause, execution continues with the next case clause, unless we return or break – for example:
function englishToFrench(english) {
let french;
switch (english) {
case 'hello':
french = 'bonjour';
case 'goodbye':
french = 'au revoir';
}
return french;
}
// The result should be 'bonjour'!
assert.equal(englishToFrench('hello'), 'au revoir');
That is, our implementation of dayOfTheWeek() only worked because we used return. We can fix englishToFrench() by using break:
function englishToFrench(english) {
let french;
switch (english) {
case 'hello':
french = 'bonjour';
break;
case 'goodbye':
french = 'au revoir';
break;
}
return french;
}
assert.equal(englishToFrench('hello'), 'bonjour'); // ok
The statements of a case clause can be omitted, which effectively gives us multiple case expressions per case clause:
function isWeekDay(name) {
switch (name) {
case 'Monday':
case 'Tuesday':
case 'Wednesday':
case 'Thursday':
case 'Friday':
return true;
case 'Saturday':
case 'Sunday':
return false;
}
}
assert.equal(isWeekDay('Wednesday'), true);
assert.equal(isWeekDay('Sunday'), false);
default clauseA default clause is jumped to if the switch expression has no other match. That makes it useful for error checking:
function isWeekDay(name) {
switch (name) {
case 'Monday':
case 'Tuesday':
case 'Wednesday':
case 'Thursday':
case 'Friday':
return true;
case 'Saturday':
case 'Sunday':
return false;
default:
throw new Error('Illegal value: '+name);
}
}
assert.throws(
() => isWeekDay('January'),
{message: 'Illegal value: January'});
Exercises:
switch
exercises/control-flow/number_to_month_test.mjs
exercises/control-flow/is_object_via_switch_test.mjs
switch: all cases exist in the same variable scopeLet’s say we want to implement a function main() that works as follows:
assert.equal(
main(['repeat', '3', 'ho']),
'hohoho'
);
assert.equal(
main(['once', 'hello']),
'hello'
);
We could implement main() like this (to reduce verbosity, error messages are omitted):
function main(args) {
const command = args[0];
if (command === undefined) {
throw new Error();
}
switch (command) {
case 'once':
const text = args[1];
if (text === undefined) {
throw new Error();
}
return text;
case 'repeat':
const timesStr = args[1];
const text = args[2]; // (A)
if (timesStr === undefined || text === undefined) {
throw new Error();
}
const times = Number(timesStr);
return text.repeat(times);
default:
throw new Error();
}
}
Alas, in line A, we get the following syntax error:
SyntaxError: Identifier 'text' has already been declared
Why is that? The complete body of switch is a single variable scope and inside it, there are two declarations for the variable text.
But this problem is easy to fix – we can create a variable scope for each switch case by wrapping its code in curly braces:
function main(args) {
const command = args[0];
if (command === undefined) {
throw new Error();
}
switch (command) {
case 'once': {
const text = args[1];
if (text === undefined) {
throw new Error();
}
return text;
}
case 'repeat': {
const timesStr = args[1];
const text = args[2]; // (A)
if (timesStr === undefined || text === undefined) {
throw new Error();
}
const times = Number(timesStr);
return text.repeat(times);
}
default:
throw new Error();
}
}
while loops ES1
A while loop has the following syntax:
while («condition») {«statements»}
Before each loop iteration, while evaluates condition:
while body is executed one more time.
while loopsThe following code uses a while loop. In each loop iteration, it removes the first element of arr via .shift() and logs it.
const arr = ['a', 'b', 'c'];
while (arr.length > 0) {
const elem = arr.shift(); // remove first element
console.log(elem);
}
Output:
abc
If the condition always evaluates to true, then while is an infinite loop:
while (true) {
if (Math.random() === 0) break;
}
do-while loops ES3
The do-while loop works much like while, but it checks its condition after each loop iteration, not before.
let input;
do {
input = prompt('Enter text:');
console.log(input);
} while (input !== ':q');
do-while can also be viewed as a while loop that runs at least once.
prompt() is a global function that is available in web browsers. It prompts the user to input text and returns it.
for loops ES1
A for loop has the following syntax:
for («initialization»; «condition»; «post_iteration») {«statements»}
The first line is the head of the loop and controls how often the body (the remainder of the loop) is executed. It has three parts and each of them is optional:
initialization: sets up variables, etc. for the loop. Variables declared here via let or const only exist inside the loop.
condition: This condition is checked before each loop iteration. If it is falsy, the loop stops.
post_iteration: This code is executed after each loop iteration.
A for loop is therefore roughly equivalent to the following while loop:
«initialization»while («condition») {«statements»«post_iteration»}
for loopsAs an example, this is how to count from zero to two via a for loop:
for (let i=0; i<3; i++) {
console.log(i);
}
Output:
012
This is how to log the contents of an Array via a for loop:
const arr = ['a', 'b', 'c'];
for (let i=0; i<arr.length; i++) {
console.log(arr[i]);
}
Output:
abc
If we omit all three parts of the head, we get an infinite loop:
for (;;) {
if (Math.random() === 0) break;
}
for-of loops ES6
A for-of loop iterates over any iterable – a data container that supports the iteration protocol. Each iterated value is stored in a variable, as specified in the head:
for («iteration_variable» of «iterable») {«statements»}
The iteration variable is usually created via a variable declaration:
const iterable = ['hello', 'world'];
for (const elem of iterable) {
console.log(elem);
}
Output:
helloworld
But we can also use a (mutable) variable that already exists:
const iterable = ['hello', 'world'];
let elem;
for (elem of iterable) {
console.log(elem);
}
const: for-of vs. forNote that in for-of loops we can use const. The iteration variable can still be different for each iteration (it just can’t change during the iteration). Think of it as a new const declaration being executed each time in a fresh scope.
In contrast, in for loops we must declare variables via let or var if their values change.
As mentioned before, for-of works with any iterable object, not just with Arrays – for example, with Sets:
const set = new Set(['hello', 'world']);
for (const elem of set) {
console.log(elem);
}
Lastly, we can also use for-of to iterate over the [index, element] entries of Arrays:
const arr = ['a', 'b', 'c'];
for (const [index, element] of arr.entries()) {
console.log(`${index} -> ${element}`);
}
Output:
0 -> a1 -> b2 -> c
With [index, element], we are using destructuring to access Array elements.
Exercise:
for-of
exercises/control-flow/array_to_string_test.mjs
for-await-of loops ES2018for-await-of is like for-of, but it works with asynchronous iterables instead of synchronous ones. And it can only be used inside async functions and async generators.
for await (const item of asyncIterable) {
// ···
}
for-await-of is described in detail in the chapter on asynchronous iteration.
for-in loops (avoid) ES1
The for-in loop visits all (own and inherited) enumerable property keys of an object. When looping over an Array, it is rarely a good choice:
The following code demonstrates these points:
const arr = ['a', 'b', 'c'];
arr.propKey = 'property value';
for (const key in arr) {
console.log(key);
}
Output:
012propKey
for-await-of.
for-of. Available in ES6+.
.forEach().
for loop to loop over an Array.
for-in to loop over an Array.