ES2018 не выглядит таким громким релизом, как ES6, но именно в таких версиях язык становится удобнее в повседневной работе. Здесь появились улучшения, которые быстро превращаются в привычку: object rest/spread, Promise.prototype.finally(), асинхронные итераторы и более сильные регулярные выражения.
Важно и то, что большая часть этих возможностей не декоративная. Они сокращают служебный код, упрощают работу с данными и убирают старые обходные пути, которые раньше приходилось писать вручную.
Ниже разберем изменения ES2018 на примерах, которые реально встречаются в приложениях и инструментах.
- Object rest и spread
- Асинхронная итерация
- Promise.prototype.finally()
- Новые возможности регулярных выражений
- Итог
Object rest и spread
В ES6 мы уже получили rest и spread для массивов. ES2018 перенес ту же идею на объекты.
Rest для объектов
rest помогает забрать часть полей отдельно, а остальные собрать в новый объект:
const user = {
id: 1,
name: "Alex",
role: "author",
city: "Madrid",
};
const { id, ...publicProfile } = user;
console.log(id); // 1
console.log(publicProfile);
// { name: "Alex", role: "author", city: "Madrid" }Это удобно, когда нужно убрать служебные поля перед отправкой данных в UI или API.
Spread для объектов
spread разворачивает свойства объекта в новый объект. Это особенно полезно для иммутабельных обновлений:
const user = {
name: "Alex",
city: "Madrid",
};
const updatedUser = {
...user,
city: "Barcelona",
role: "author",
};
console.log(updatedUser);
// { name: "Alex", city: "Barcelona", role: "author" }Если одинаковый ключ встречается несколько раз, побеждает последнее значение.
Асинхронная итерация
for await...of позволяет последовательно читать асинхронный источник данных. Чаще всего его используют с async iterable, но он также умеет ждать значения из массива промисов.
const requests = [
Promise.resolve("first"),
Promise.resolve("second"),
Promise.resolve("third"),
];
async function printInOrder() {
for await (const value of requests) {
console.log(value);
}
}
printInOrder();
// first
// second
// thirdВажно понимать ограничение: for await...of ждет значение перед переходом к следующей итерации. Если нужна именно параллельная загрузка, сначала запускай все операции, а потом собирай результат через Promise.all.
Promise.prototype.finally()
До ES2018 одинаковый код очистки приходилось дублировать и в then, и в catch. finally() убирает это повторение:
fetch("https://api.github.com/users/oleksiimyzgin")
.then((response) => response.json())
.then((data) => {
console.log(data.login);
})
.catch((error) => {
console.error(error);
})
.finally(() => {
console.log("request finished");
});finally() не получает результат промиса. Его задача не обработать данные, а выполнить код, который должен сработать в любом случае: скрыть loader, освободить ресурс, завершить таймер или сбросить флаг состояния.
Новые возможности регулярных выражений
ES2018 заметно усилил RegExp. Самые полезные изменения: lookbehind, именованные группы, Unicode property escapes и флаг s.
Lookbehind
lookahead проверяет, что нужная строка идет после текущего совпадения. lookbehind делает то же самое, но в обратную сторону.
const lookAhead = /Java(?=Script)/;
const lookBehind = /(?<=Java)Script/;
console.log(lookAhead.test("JavaScript")); // true
console.log(lookAhead.test("Java Script")); // false
console.log(lookBehind.test("JavaScript")); // true
console.log(lookBehind.test("TypeScript")); // falseОтрицательный lookbehind тоже работает:
const notAfterJava = /(?<!Java)Script/;
console.log(notAfterJava.test("JavaScript")); // false
console.log(notAfterJava.test("TypeScript")); // trueИменованные группы
С именованными группами результат регулярного выражения становится заметно понятнее:
const datePattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = datePattern.exec("2019-04-10");
console.log(match.groups.year); // "2019"
console.log(match.groups.month); // "04"
console.log(match.groups.day); // "10"Это особенно полезно в сложных шаблонах, где доступ к match[1] и match[2] быстро превращается в источник ошибок.
Unicode property escapes
\\p{...} и \\P{...} позволяют описывать классы символов через свойства Unicode:
const asciiOnly = /^\p{ASCII}+$/u;
const greekOnly = /^\p{Script=Greek}+$/u;
const emojiOnly = /^\p{Emoji}+$/u;
console.log(asciiOnly.test("ABC123")); // true
console.log(asciiOnly.test("ABC🙃")); // false
console.log(greekOnly.test("ελληνικά")); // true
console.log(greekOnly.test("hello")); // false
console.log(emojiOnly.test("🙃🙃")); // true
console.log(emojiOnly.test("A")); // falseФлаг s
Флаг s включает режим dotAll: точка начинает совпадать и с символом новой строки.
const withoutDotAll = /hi.welcome/;
const withDotAll = /hi.welcome/s;
console.log(withoutDotAll.test("hi\nwelcome")); // false
console.log(withDotAll.test("hi\nwelcome")); // trueИтог
ES2018 не меняет язык радикально, но сильно улучшает ежедневную работу. object rest/spread делают обновление данных чище, finally() убирает дублирование, for await...of упрощает работу с асинхронными потоками, а RegExp впервые становятся заметно удобнее для серьезных задач.
Если выбираешь, что внедрять в старом коде в первую очередь, начинай с object rest/spread и finally() — изменения минимальные, а читаемость растет сразу.