Управління потоком виконання та обробка помилок

Ви читаєте англійську версію цього вмісту, бо ще не існує перекладу для цієї мови. Допоможіть нам перекласти цю статтю!

JavaScript підтримує компактний набір інструкцій, зокрема інструкцій для управління потоком, які можна використати, щоб включити інтерактивність до вашого додатку. Цей розділ надає огляд таких інструкцій.

Довідник JavaScript містить вичерпні дані про інструкції, описані у цьому розділі. Символ крапки з комою (;) розділяє інструкції у JavaScript-коді.

Будь-який вираз у JavaScript також являється інструкцією. Повну інформацію про вирази дивіться у розділі Вирази та оператори.

Блокова інструкція

Найбільш базовою інструкцією є блокова, що об'єднує декілька інструкцій в одну. Блок обмежується парою фігурних дужок:

{
  інструкція_1;
  інструкція_2;
  .
  .
  .
  інструкція_n;
}

Приклад

Блокова інструкція зазвичай використовується поряд з інструкціями для управління потоком виконання (наприклад, if, for, while).

while (x < 10) {
  x++;
}

У даному випадку, { x++; } являється блоковою інструкцією.

Важливо: JavaScript до ECMAScript2015 не має блокової області видимості. Змінні, об'явлені всередині блоку прив'язуються до області меж зовнішньої функції, або ж усього скрипта. І ефект від їх задання поширюється за межі блоку. Інакше кажучи, блокові інструкції не задають області видимості. "Поодинокі" блоки у JavaScript можуть призвести до абсолютно відмінних результатів від аналогічного коду на C чи Java. Наприклад:

var x = 1;
{
  var x = 2;
}
console.log(x); // в результаті - 2

В результаті отримуємо 2 тому, що інструкція var x всередині блоку знаходиться в одній області видимості з інструкцією var x перед блоком. У C чи Java такий подібний код поверне 1.

Починаючи з ECMAScript2015, декларації змінних let і const мають блокову область видимості. Докладніше на довідкових сторінках let і const.

Умовні інструкції

Умовною інструкцією називається набір команд, що виконаються якщо певна умова буде істинною. JavaScript підтримує два види умовних інструкцій: if...else та switch.

Інструкція if...else

Використовуйте if, щоб виконати інструкцію, якщо логічна умова являється істинною. Використовуйте необов'яковий else, щоб виконати інструкцію, якщо умова являється хибною. Інструкція if виглядає так:

if (умова) {
  інструкція_1;
} else {
  інструкція_2;
}

Тут умова може бути будь-яким виразом, що зводиться до true чи false (дивіться розділ Boolean для роз'яснення, що і як обчислюється до true чи false). Якщо умова обчислюється до true, виконується інструкція_1; інакше виконується інструкція_2. інструкція_1 та інструкція_2 можуть бути будь-якими, включаючи вкладені інструкції if.

Можна також суміщувати інструкції у вигляді else if, щоб послідовно перевірити декілька умов, як-от, наприклад:

if (умова_1) {
  інструкція_1;
} else if (умова_2) {
  інструкція_2;
} else if (умова_n) {
  інструкція_n;
} else {
  інструкція_остання;
} 

У випадку наявності декількох таких умов буде виконано лише найперший обчислений до true. Щоб виконати декілька інструкцій, слід об'єднати їх у блок ({ ... }) . Загалом, хорошою практикою вважається завжди використовувати блоки інструкцій, особливо при вкладенні інструкцій if:

if (умова) {
  інструкція_1_виконується_якщо_умова_вірна;
  інструкція_2_виконується_якщо_умова_вірна;
} else {
  інструкція_3_виконується_якщо_умова_невірна;
  інструкція_4_виконується_якщо_умова_невірна;
}
Не рекомендується використовувати звичайні присвоєння в умовних виразах, так як присвоєння можна сплутати з порівнянням при перегляді коді. Наприклад, не слід писати ось так:
if (x = y) {
  /* statements here */
}

Якщо все ж таки потрібно використати присвоєння всередині умовного виразу, загальною практикою є додавання зайвих дужок навколо присвоєння, як-от:

if ((x = y)) {
  /* якісь інструкції */
}

Хибні значення

Наступні значення обчислюються до false (також знані як Falsy значення):

  • false
  • undefined
  • null
  • 0
  • NaN
  • порожня стрічка ("")

Всі інші значення (включно з усіма об'єктами), обчислюються до true при передачі в умовний вираз.

Не плутайте примітивні булеві значення true та false із істинними і хибними значеннями об'єкту Boolean. Наприклад:

var b = new Boolean(false);
if (b) // цей умовний вираз буде істинним
if (b == true) // цей умовний вираз буде хибним

Приклад

У наведеному далі прикладі функція checkData повертає true, якщо у об'єкті  Text три символи, а інакше вона показує сповіщення та повертає false.

function checkData() {
  if (document.form1.threeChar.value.length == 3) {
    return true;
  } else {
    alert('Enter exactly three characters. ' +
    document.form1.threeChar.value + ' is not valid.');
    return false;
  }
}

switch statement

A switch statement allows a program to evaluate an expression and attempt to match the expression's value to a case label. If a match is found, the program executes the associated statement. A switch statement looks as follows:

switch (expression) {
  case label_1:
    statements_1
    [break;]
  case label_2:
    statements_2
    [break;]
    ...
  default:
    statements_def
    [break;]
}

The program first looks for a case clause with a label matching the value of expression and then transfers control to that clause, executing the associated statements. If no matching label is found, the program looks for the optional default clause, and if found, transfers control to that clause, executing the associated statements. If no default clause is found, the program continues execution at the statement following the end of switch. By convention, the default clause is the last clause, but it does not need to be so.

The optional break statement associated with each case clause ensures that the program breaks out of switch once the matched statement is executed and continues execution at the statement following switch. If break is omitted, the program continues execution at the next statement in the switch statement.

Example

In the following example, if fruittype evaluates to "Bananas", the program matches the value with case "Bananas" and executes the associated statement. When break is encountered, the program terminates switch and executes the statement following switch. If break were omitted, the statement for case "Cherries" would also be executed.

switch (fruittype) {
  case 'Oranges':
    console.log('Oranges are $0.59 a pound.');
    break;
  case 'Apples':
    console.log('Apples are $0.32 a pound.');
    break;
  case 'Bananas':
    console.log('Bananas are $0.48 a pound.');
    break;
  case 'Cherries':
    console.log('Cherries are $3.00 a pound.');
    break;
  case 'Mangoes':
    console.log('Mangoes are $0.56 a pound.');
    break;
  case 'Papayas':
    console.log('Mangoes and papayas are $2.79 a pound.');
    break;
  default:
   console.log('Sorry, we are out of ' + fruittype + '.');
}
console.log("Is there anything else you'd like?");

Exception handling statements

You can throw exceptions using the throw statement and handle them using the try...catch statements.

Exception types

Just about any object can be thrown in JavaScript. Nevertheless, not all thrown objects are created equal. While it is fairly common to throw numbers or strings as errors it is frequently more effective to use one of the exception types specifically created for this purpose:зроблений ленд, зроблені декілька екранів? чи прям все включаючи адаптив?

оператор throw

Скористайтеся оператором throw, щоб викинути виняток. Коли викидаєте виняток, ви вказуєте вираз, що містить значення, яке викидається:

throw expression;

Ви можете викинути будь-який вираз, не тільки вирази окремих типів. Наступний код викидає кілька винятків різних типів:

throw 'Error2';   // тип String
throw 42;         // тип Number
throw true;       // тип Boolean
throw {toString: function() { return "Я об'єкт!"; } };
Примітка: Ви можете вказати об'єкт, коли викидаєте виняток. Після цього ви можете звертатися до властивостей об'єкта у блоці catch.
// Створити об'єкт UserException
function UserException(message) {
  this.message = message;
  this.name = 'UserException';
}

// Перетворити виняток у гарний рядок при використанні в якості рядка
// (наприклад, у консолі помилок)
UserException.prototype.toString = function() {
  return this.name + ': "' + this.message + '"';
}

// Створити екземпляр об'єкта та викинути його
throw new UserException('Значення завелике');

try...catch statement

The try...catch statement marks a block of statements to try, and specifies one or more responses should an exception be thrown. If an exception is thrown, the try...catch statement catches it.

The try...catch statement consists of a try block, which contains one or more statements, and a catch block, containing statements that specify what to do if an exception is thrown in the try block. That is, you want the try block to succeed, and if it does not succeed, you want control to pass to the catch block. If any statement within the try block (or in a function called from within the try block) throws an exception, control immediately shifts to the catch block. If no exception is thrown in the try block, the catch block is skipped. The finally block executes after the try and catch blocks execute but before the statements following the try...catch statement.

The following example uses a try...catch statement. The example calls a function that retrieves a month name from an array based on the value passed to the function. If the value does not correspond to a month number (1-12), an exception is thrown with the value "InvalidMonthNo" and the statements in the catch block set the monthName variable to unknown.

function getMonthName(mo) {
  mo = mo - 1; // Adjust month number for array index (1 = Jan, 12 = Dec)
  var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
                'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  if (months[mo]) {
    return months[mo];
  } else {
    throw 'InvalidMonthNo'; //throw keyword is used here
  }
}

try { // statements to try
  monthName = getMonthName(myMonth); // function could throw exception
}
catch (e) {
  monthName = 'unknown';
  logMyErrors(e); // pass exception object to error handler -> your own function
}

The catch block

You can use a catch block to handle all exceptions that may be generated in the try block.

catch (catchID) {
  statements
}

The catch block specifies an identifier (catchID in the preceding syntax) that holds the value specified by the throw statement; you can use this identifier to get information about the exception that was thrown. JavaScript creates this identifier when the catch block is entered; the identifier lasts only for the duration of the catch block; after the catch block finishes executing, the identifier is no longer available.

For example, the following code throws an exception. When the exception occurs, control transfers to the catch block.

try {
  throw 'myException'; // generates an exception
}
catch (e) {
  // statements to handle any exceptions
  logMyErrors(e); // pass exception object to error handler
}

The finally block

The finally block contains statements to execute after the try and catch blocks execute but before the statements following the try...catch statement. The finally block executes whether or not an exception is thrown. If an exception is thrown, the statements in the finally block execute even if no catch block handles the exception.

You can use the finally block to make your script fail gracefully when an exception occurs; for example, you may need to release a resource that your script has tied up. The following example opens a file and then executes statements that use the file (server-side JavaScript allows you to access files). If an exception is thrown while the file is open, the finally block closes the file before the script fails.

openMyFile();
try {
  writeMyFile(theData); //This may throw an error
} catch(e) {  
  handleError(e); // If we got an error we handle it
} finally {
  closeMyFile(); // always close the resource
}

If the finally block returns a value, this value becomes the return value of the entire try-catch-finally production, regardless of any return statements in the try and catch blocks:

function f() {
  try {
    console.log(0);
    throw 'bogus';
  } catch(e) {
    console.log(1);
    return true; // this return statement is suspended
                 // until finally block has completed
    console.log(2); // not reachable
  } finally {
    console.log(3);
    return false; // overwrites the previous "return"
    console.log(4); // not reachable
  }
  // "return false" is executed now  
  console.log(5); // not reachable
}
f(); // console 0, 1, 3; returns false

Overwriting of return values by the finally block also applies to exceptions thrown or re-thrown inside of the catch block:

function f() {
  try {
    throw 'bogus';
  } catch(e) {
    console.log('caught inner "bogus"');
    throw e; // this throw statement is suspended until 
             // finally block has completed
  } finally {
    return false; // overwrites the previous "throw"
  }
  // "return false" is executed now
}

try {
  f();
} catch(e) {
  // this is never reached because the throw inside
  // the catch is overwritten
  // by the return in finally
  console.log('caught outer "bogus"');
}

// OUTPUT
// caught inner "bogus"

Nesting try...catch statements

You can nest one or more try...catch statements. If an inner try...catch statement does not have a catch block, it needs to have a finally block and the enclosing try...catch statement's catch block is checked for a match. For more information, see nested try-blocks on the try...catch reference page.

Utilizing Error objects

Depending on the type of error, you may be able to use the 'name' and 'message' properties to get a more refined message. 'name' provides the general class of Error (e.g., 'DOMException' or 'Error'), while 'message' generally provides a more succinct message than one would get by converting the error object to a string.

If you are throwing your own exceptions, in order to take advantage of these properties (such as if your catch block doesn't discriminate between your own exceptions and system ones), you can use the Error constructor. For example:

function doSomethingErrorProne() {
  if (ourCodeMakesAMistake()) {
    throw (new Error('The message'));
  } else {
    doSomethingToGetAJavascriptError();
  }
}
....
try {
  doSomethingErrorProne();
} catch (e) {
  console.log(e.name); // logs 'Error'
  console.log(e.message); // logs 'The message' or a JavaScript error message)
}

Promises

Starting with ECMAScript2015, JavaScript gains support for Promise objects allowing you to control the flow of deferred and asynchronous operations.

A Promise is in one of these states:

  • pending: initial state, not fulfilled or rejected.
  • fulfilled: successful operation
  • rejected: failed operation.
  • settled: the Promise is either fulfilled or rejected, but not pending.

Loading an image with XHR

A simple example using Promise and XMLHttpRequest to load an image is available at the MDN GitHub js-examples repository. You can also see it in action. Each step is commented and allows you to follow the Promise and XHR architecture closely. Here is the uncommented version, showing the Promise flow so that you can get an idea:

function imgLoad(url) {
  return new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();
    request.open('GET', url);
    request.responseType = 'blob';
    request.onload = function() {
      if (request.status === 200) {
        resolve(request.response);
      } else {
        reject(Error('Image didn\'t load successfully; error code:' 
                     + request.statusText));
      }
    };
    request.onerror = function() {
      reject(Error('There was a network error.'));
    };
    request.send();
  });
}

For more detailed information, see the Promise reference page and the Using Promises guide.