Новый оператор в JavaScript — это революция!

Новый оператор в 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 стала гораздо более удобной и интуитивно понятной с новым оператором безопасного присваивания (?=).

Используйте его, чтобы писать более чистый и предсказуемый код!

Статья была переведена с этого ресурса