Обычно скрипт в случае ошибки «падает» (сразу же останавливается), с выводом ошибки в консоль.
Но есть синтаксическая конструкция try..catch, которая позволяет «ловить» ошибки и вместо падения делать что-то более осмысленное.
Конструкция try..catch состоит из двух основных блоков: try, и затем catch:
try {
// код...
} catch (err) {
// обработка ошибки
}
Работает она так:
-
Сначала выполняется код внутри блока try {...}.
-
Если в нём нет ошибок, то блок catch(err) игнорируется: выполнение доходит до конца try и потом далее, полностью пропуская catch.
-
Если же в нём возникает ошибка, то выполнение try прерывается, и поток управления переходит в начало catch(err).
Переменная err (можно использовать любое имя) содержит объект ошибки с подробной информацией о произошедшем.
Таким образом, при ошибке в блоке try {…} скрипт не «падает», и мы получаем возможность обработать ошибку внутри catch.
Давайте рассмотрим примеры.
- Пример без ошибок: выведет alert (1) и (2):
try {
alert('Начало блока try'); // (1) <--
// ...код без ошибок
alert('Конец блока try'); // (2) <--
} catch(err) {
alert('Catch игнорируется, так как нет ошибок'); // (3)
- Пример с ошибками: выведет (1) и (3):
try {
alert('Начало блока try'); // (1) <--
lalala; // ошибка, переменная не определена!
alert('Конец блока try (никогда не выполнится)'); // (2)
} catch(err) {
alert(`Возникла ошибка!`); // (3) <--
}
Чтобы try..catch работал, код должен быть выполнимым.
Другими словами, это должен быть корректный JavaScript-код.
try {
{{{{{{{{{{{{
} catch(e) {
alert("Движок не может понять этот код, он некорректен");
}
Исключение, которое произойдёт в коде, запланированном «на будущее», например в setTimeout, try..catch не поймает:
try {
setTimeout(function() {
noSuchVariable; // скрипт упадёт тут
}, 1000);
} catch (e) {
alert( "не сработает" );
}
Это потому, что функция выполняется позже, когда движок уже покинул конструкцию try..catch.
try {
// ...
} catch(err) { // <-- объект ошибки, можно использовать другое название вместо err
// ...
}
Для всех встроенных ошибок этот объект имеет два основных свойства:
- name
Имя ошибки. Например, для неопределённой переменной это "ReferenceError".
- message
Текстовое сообщение о деталях ошибки.
let json = "{ некорректный JSON }";
try {
let user = JSON.parse(json); // <-- тут возникает ошибка...
alert( user.name ); // не сработает
} catch (e) {
// ...выполнение прыгает сюда
alert( "Извините, в данных ошибка, мы попробуем получить их ещё раз." );
alert( e.name );
alert( e.message );
}
Что если json синтаксически корректен, но не содержит необходимого свойства
let json = '{ "age": 30 }'; // данные неполны
try {
let user = JSON.parse(json); // <-- выполнится без ошибок
alert( user.name ); // нет свойства name!
} catch (e) {
alert( "не выполнится" );
}
Оператор throw генерирует ошибку.
Синтаксис:
throw <объект ошибки>
Пример:
let error = new Error(" Ого, ошибка! o_O");
alert(error.name); // Error
alert(error.message); // Ого, ошибка! o_O
Переделаем прошлый вариант:
let json = '{ "age": 30 }'; // данные неполны
try {
let user = JSON.parse(json); // <-- выполнится без ошибок
if (!user.name) {
throw new SyntaxError("Данные неполны: нет имени"); // (*)
}
alert( user.name );
} catch(e) {
alert( "JSON Error: " + e.message ); // JSON Error: Данные неполны: нет имени
}
Конструкция try..catch может содержать ещё одну секцию: finally.
Если секция есть, то она выполняется в любом случае:
-
после try, если не было ошибок,
-
после catch, если ошибки были.
try {
... пробуем выполнить код...
} catch(e) {
... обрабатываем ошибки ...
} finally {
... выполняем всегда ...
}
Попробуйте запустить такой код:
try {
alert( 'try' );
if (confirm('Сгенерировать ошибку?')) BAD_CODE();
} catch (e) {
alert( 'catch' );
} finally {
alert( 'finally' );
}
Существует специальный синтаксис для работы с промисами, который называется «async/await»
Давайте посмотри на примере запросов
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(data => console.log(data));
-
Делаем запрос, получаем промис.
-
После того как промис выполнен успешно, получаем специальный объект ответа. Выполним метод объекта ответа json(), чтобы получить данные.
-
Метод json() возвращает промис, так что далее снова вызываем then и в колбеке выводим данные в консоль.
Все просто 😅
Используя Async/Await можем сделать так:
async getPost = () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const data = await response.json();
console.log(data);
}
Мы обернули наш вызов в асинхронную функцию (async), заменили колбеки (then) на заявление await.
Теперь код останавливает выполнение на ключевых словах await, до выполнения обещания, и продолжает выполнение далее.
Асинхронное программирование как синхронное!
На самом же деле Async/Await это просто синтаксический сахар
Async/Await действительно позволяет писать асинхронный код синхронно, не блокируя стек вызовов.
Мы замораживаем код и ждем пока выполнится промис, а затем продолжаем.
Еще пример:
С использованием промисов
const makeRequest = () =>
getJSON()
.then(data => {
console.log(data)
return "done"
})
makeRequest()
Вот как то же самое делается с использованием async/await:
const makeRequest = async () => {
console.log(await getJSON())
return "done"
}
makeRequest()
У слова async один простой смысл: эта функция всегда возвращает промис. Значения других типов оборачиваются в завершившийся успешно промис автоматически.
Например, эта функция возвратит выполненный промис с результатом 1:
async function f() {
return 1;
}
f().then(alert); // 1
Можно и явно вернуть промис, результат будет одинаковым:
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
// работает только внутри async–функций
let value = await promise;
Ключевое слово await заставит интерпретатор JavaScript ждать до тех пор, пока промис справа от await не выполнится.
После чего оно вернёт его результат, и выполнение кода продолжится.
В этом примере промис успешно выполнится через 1 секунду:
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("готово!"), 1000)
});
let result = await promise; // будет ждать, пока промис не выполнится (*)
alert(result); // "готово!"
}
f();
В данном примере выполнение функции остановится на строке (let result) до тех пор, пока промис не выполнится. Это произойдёт через секунду после запуска функции. После чего в переменную result будет записан результат выполнения промиса
**Обратите внимание, хотя await и заставляет JavaScript дожидаться выполнения промиса, это не отнимает ресурсов процессора. **
Пока промис не выполнится, JS-движок может заниматься другими задачами: выполнять прочие скрипты, обрабатывать события и т.п.
Задача:
Давайте напишем промис, который будет генерировать рандомное число. Если это рандомное число меньше чем 0.6, то вызываем resolve('Good'), а если больше, то вызываем reject('Not Good').
Потом надо создать асинхронную функцию handler, которая будет обрабатывать наш промис:
-
Если промис выполнился успешно, то возвращаем 'Good result'
-
Если промис выполнился с ошибкой, то возвращаем 'Bad result'
И выводим результат в консоль
Еще пример на обработку ошибки
const promise = () => {
return new Promise((res, rej) => {
const random = Math.random();
if (random < .6) {
setTimeout(() => res('Good'), 1000)
} else {
setTimeout(() => rej('Not Good'), 1000)
}
})
};
const handler = async () => {
try {
await promise();
return 'Good result'
} catch (err) {
return 'Bad result'
}
};
handler()
.then(item => {
console.log(item)
});
Задача:
Делаем все через async/await
Давайте сделаем инпут, в который будет вводить ник юзера с гитхаба (к примеру свой).
Давайте еще сделаем кнопку Search, по нажатию на которую мы отправим запрос.
Эндпоинт на сервер: https://api.github.com/users/НИКНЕЙМ
После получаения данные давайте отобразим на экране аватарку юзера и его статус.
const request = async () => {
try {
const response = await fetch('https://api.github.com/users/11123dsf');
const jsonData = await response.json();
if (jsonData.message === 'Not Found') {
throw new Error('User Not Found')
}
return 'DONE'
} catch (err) {
console.log('err: ', err)
return 'ERROR'
}
}
const res = request();
res.then(item => console.log(item))
Базовый объект для работы с бинарными данными имеет тип ArrayBuffer и представляет собой ссылку на непрерывную область памяти фиксированной длины.
Вот так мы можем создать его экземпляр:
let buffer = new ArrayBuffer(16); // создаётся буфер длиной 16 байт
alert(buffer.byteLength); // 16
Инструкция выше выделяет непрерывную область памяти размером 16 байт и заполняет её нулями.
Давайте внесём ясность, чтобы не запутаться. ArrayBuffer не имеет ничего общего с Array:
-
его длина фиксирована, мы не можем увеличивать или уменьшать её.
-
ArrayBuffer занимает ровно столько места в памяти, сколько указывается при создании.
-
Для доступа к отдельным байтам нужен вспомогательный объект-представление, buffer[index] не сработает.
Такие объекты не хранят какое-то собственное содержимое.
Они интерпретируют бинарные данные, хранящиеся в ArrayBuffer.
Например:
-
Uint8Array – представляет каждый байт в ArrayBuffer как отдельное число; возможные значения находятся в промежутке от 0 до 255 (в байте 8 бит, отсюда такой набор). Такое значение называется «8-битное целое без знака».
-
Uint16Array – представляет каждые 2 байта в ArrayBuffer как целое число; возможные значения находятся в промежутке от 0 до 65535. Такое значение называется «16-битное целое без знака».
-
Uint32Array – представляет каждые 4 байта в ArrayBuffer как целое число; возможные значения находятся в промежутке от 0 до 4294967295. Такое значение называется «32-битное целое без знака».
-
Float64Array – представляет каждые 8 байт в ArrayBuffer как число с плавающей точкой; возможные значения находятся в промежутке между 5.0x10-324 и 1.8x10308.
ArrayBuffer – это корневой объект, основа всего, необработанные бинарные данные.
Но если мы собираемся что-то записать в него или пройтись по его содержимому, да и вообще для любых действий мы должны использовать какой-то объект-представление («view»), например:
let buffer = new ArrayBuffer(16); // создаётся буфер длиной 16 байт
let view = new Uint32Array(buffer); // интерпретируем содержимое как последовательность 32-битных целых чисел без знака
alert(Uint32Array.BYTES_PER_ELEMENT); // 4 байта на каждое целое число
alert(view.length); // 4, именно столько чисел сейчас хранится в буфере
alert(view.byteLength); // 16, размер содержимого в байтах
// давайте запишем какое-нибудь значение
view[0] = 123456;
// теперь пройдёмся по всем значениям
for(let num of view) {
alert(num); // 123456, потом 0, 0, 0 (всего 4 значения)
}
FileReader объект, цель которого читать данные из File
Данные передаются при помощи событий, так как чтение с диска может занять время.
Конструктор:
let reader = new FileReader(); // без аргументов
Основные методы:
-
readAsArrayBuffer(blob) – считать данные как ArrayBuffer
-
readAsText(blob, [encoding]) – считать данные как строку (кодировка по умолчанию: utf-8)
-
readAsDataURL(blob) – считать данные как base64-кодированный URL.
-
abort() – отменить операцию.
В процессе чтения происходят следующие события:
-
loadstart – чтение начато.
-
progress – срабатывает во время чтения данных.
-
load – нет ошибок, чтение окончено.
-
abort – вызван abort().
-
error – произошла ошибка.
-
loadend – чтение завершено (успешно или нет).
Когда чтение закончено, мы сможем получить доступ к его результату следующим образом:
-
reader.result результат чтения (если оно успешно)
-
reader.error объект ошибки (при неудаче).
Вот пример чтения файла:
<input type="file" onchange="readFile(this)">
<script>
function readFile(input) {
let file = input.files[0];
let reader = new FileReader();
reader.readAsText(file);
reader.onload = function() {
console.log(reader.result);
};
reader.onerror = function() {
console.log(reader.error);
};
}
</script>
Пример:
<div id="example">
...Текст...
</div>
<style>
#example {
width: 300px;
height: 200px;
border: 25px solid #E8C48F;
padding: 20px;
overflow: auto;
}
</style>
Вот общая картина с геометрическими свойствами:
Эти два свойства – самые простые. Они содержат «внешнюю» ширину/высоту элемента, то есть его полный размер, включая рамки
Пойдём дальше. Внутри элемента у нас рамки (border).
Для них есть свойства-метрики clientTop и clientLeft.
В нашем примере:
-
clientLeft = 25 – ширина левой рамки
-
clientTop = 25 – ширина верхней рамки
Эти свойства – размер области внутри рамок элемента.
Они включают в себя ширину области содержимого вместе с внутренними отступами padding, но без прокрутки:
Если нет внутренних отступов padding, то clientWidth/Height в точности равны размеру области содержимого внутри рамок и полосы прокрутки (если она есть).
Эти свойства – как clientWidth/clientHeight, но также включают в себя прокрученную (которую не видно) часть элемента.
Эти свойства можно использовать, чтобы «распахнуть» элемент на всю ширину/высоту.
// распахнуть элемент на всю высоту
element.style.height = `${element.scrollHeight}px`;