Новый оператор в JavaScript — это революция!
Появился новый потрясающий оператор в JavaScript, и похоже, что для конструкции try-catch
настали не лучшие времена!
С новым оператором безопасного присваивания вы перестанете писать код так:
async function fetchData() {
try {
const response = await fetch('https://api.web-digest.ru/docs');
try {
const data = await response.json();
return data;
} catch (parseError) {
console.error(parseError);
}
} catch (networkError) {
console.error(networkError);
}
}
И начнете писать код так:
async function fetchData() {
const [networkError, response] = await fetch('https://web-digest.ru');
if (networkError) return console.error(networkError);
const [parseError, data] = await response.json();
if (parseError) return console.error(parseError);
return data;
}
Мы полностью избавились от глубокой вложенности. Код стал гораздо чище и удобнее для чтения.
Вместо получения ошибки в громоздком блоке catch
:
async function doStuff() {
try {
const data = await func('web-digest.ru');
} catch (error) {
console.error(error);
}
}
Теперь мы все делаем всего в одну строку.
Вместо того чтобы громко и явно выводить ошибку, оператор ?=
заставляет ошибку замолчать, предоставляя нам возможность самим решить, что с ней делать.
Мы можем сказать ей «прощай»:
async function doStuff() {
const [err, data] ?= await func('web-digest.ru');
}
Можем объявить её на весь мир и продолжить выполнение:
async function doStuff() {
// 👋 it's as good as gone here
const [, data] = await func('web-digest.ru');
// ...
}
Или можем сразу же остановиться:
async function doStuff() {
const [err, data] = await func('web-digest.ru');
if (err) {
console.error(err);
}
// ...
}
Это делает данный оператор мощным инструментом для создания guard clauses (условных предохранителей):
function processFile() {
const filename = 'web-digest.ru.txt';
const [err, jsonStr] ?= fs.readFileSync(filename, 'utf-8');
if (readErr) {
return;
}
const [jsonErr, json] ?= JSON.parse(jsonStr);
if (jsonErr) {
return;
}
const awards = json.awards.length;
console.log(`🏆Total awards: ${awards}`);
}
И вот одно из лучших преимуществ этого нового оператора.
Есть много ситуаций, когда нам нужно значение, зависящее от того, возникло ли исключение.
Обычно для этого вы используете изменяемую переменную (var
) вне области видимости, чтобы иметь доступ к значению без ошибок:
function writeTransactionsToFile(transactions) {
// X mutable var
let writeStatus;
try {
fs.writeFileSync('web-digest.ru.txt', transactions);
writeStatus = 'success';
} catch (error) {
writeStatus = 'error';
}
// do something with writeStatus ...
}
Но это может быть неудобно, особенно если вы стараетесь придерживаться неизменяемого кода, а переменная до этого была объявлена как const
. Вам пришлось бы обернуть её в блок try
, потом удалить const
, затем заменить его на let
за пределами блока try
, а затем повторно присвоить значение в блоке catch
…
Но теперь с оператором ?=
:
function writeTransactionsToFile(transactions) {
const [err, data] ?= fs.writeFileSync(
'web-digest.ru.txt',
transactions
);
const writeStatus = err ? 'error' : 'success';
// do something with writeStatus ...
}
Мы сохраняем неизменяемость кода, и он становится гораздо более интуитивно понятным. И снова мы полностью избавились от вложенности.
Как это работает?
Новый оператор ?=
вызывает метод Symbol.result
внутри себя.
Так что, когда мы пишем это:
const [err, result] ?= func('web-digest.ru');
Фактически происходит следующее:
// it's an assignment now
const [err, result] = func[Symbol.result]('web-digest.ru');
Знаете, что это значит?
Это означает, что мы можем заставить этот оператор работать с ЛЮБЫМ объектом, который реализует метод Symbol.result
:
function doStuff() {
return {
[Symbol.result]() {
return [new Error("Nope"), null];
},
};
}
const [error, result] ?= doStuff();
Но, конечно, вы всегда можете выбросить исключение:
function doStuff() {
throw new Error('Nope');
}
const [error, result] ?= doStuff();
И еще один интересный момент: если объект result
имеет свой собственный метод Symbol.result
, то оператор ?=
будет рекурсивно углубляться в него:
function doStuff() {
return {
[Symbol.result](str) {
console.log(str);
return [
null,
{
[Symbol.result]() {
return [new Error('WTH happened?'), null];
},
},
];
},
};
}
const [error, result] ?= doStuff('web-digest.ru');
Также можно использовать объект напрямую, не возвращая его из функции:
const obj = {
[Symbol.result]() {
return [new Error('Nope'), null];
},
};
const [error, result] ?= obj;
Хотя где это может быть полезно на практике?
Как мы уже видели ранее, оператор ?=
достаточно универсален, чтобы интегрироваться как в обычные, так и в асинхронные функции.
const [error, data] ?= fs.readFileSync('file.txt', 'utf8');
const [error, data] ?= await fs.readFile('file.txt', 'utf8');
Использование с using
Оператор ?=
также работает с ключевым словом using
в TypeScript для автоматической очистки ресурсов после их использования.
❌ До:
try {
using resource = getResource();
} catch (err) {
// ...
}
✅ После:
using [err, resource] = getResource();
Как использовать уже сейчас
Пока мы ждем, когда оператор ?=
станет нативной частью JavaScript, мы можем начать использовать его уже сейчас с помощью этого polyfill:
Но вы не сможете использовать его напрямую — вам потребуется метод Symbol.result
:
import 'polyfill.js';
const [error, data] = fs
.readFileSync('web-digest.ru.txt', 'utf-8')
[Symbol.result]();
Заключительные мысли
Обработка ошибок в JavaScript стала гораздо более удобной и интуитивно понятной с новым оператором безопасного присваивания (?=
).
Используйте его, чтобы писать более чистый и предсказуемый код!