С момента выхода стандарта ECMAScript 2015 (его ещё называют ES6) JavaScript серьёзно изменился и улучшился. Это очень хорошая новость для всех JS-разработчиков. Более того, теперь новая версия ECMAScript выходит каждый год. Возможно, вы не обратили особого внимания на то, что появилось в самой свежей версии стандарта, который был выпущен в июне 2019 года. Автор заметки, перевод которой мы сегодня публикуем, хочет в двух словах рассказать о новшествах JavaScript, и о том, чего можно ждать в следующей версии стандарта ECMAScript.
Здесь будут упомянуты возможности, предложения которых находятся на третьем этапе согласования (Stage 3). Это значит, что они, скорее всего, появятся в следующей версии стандарта ECMAScript, но с абсолютной достоверностью этого утверждать нельзя. Вот репозиторий, в котором можно найти сведения о предложениях, находящихся на разных этапах согласования.
Возможности ECMAScript 2019 (ES10)
В стандарте ES10 появилось немало новых возможностей. Здесь мы рассмотрим лишь некоторые из них. А именно — пару новых методов массивов.
▍Метод Array.prototype.flat
Этот метод позволяет делать массивы, в состав которых входят другие массивы, более «плоскими», «сжимая» их до заданного уровня глубины.
const array = [1, 2, [3, 4]];
array.flat(); // [1, 2, 3, 4];
Это — очень полезная возможность, в особенности в тех случаях, когда нужно работать с вложенными массивами. Если глубина вложенности массивов в структуре данных превышает 1, то однократный вызов метода flat
не сможет сделать массив полностью «плоским». Этот метод принимает необязательный параметр depth
, который позволяет указать то, на сколько уровней вложенности должна быть уменьшена мерность обрабатываемого массива.
// Сумасшедший пример
const crazyArray = [1, 2, [3, 4], [[5], [6, [7,8]]]];
crazyArray.flat(Infinity); // [1, 2, 3, 4, 5, 6, 7, 8];
// Аргумент, передаваемый flat, должен иметь числовой тип
Чем глубже массив — тем больше вычислительных ресурсов понадобится для его обработки. Обратите внимание на то, что эту возможность не поддерживают IE и Edge.
▍Метод Array.prototype.flatMap
Этот метод сначала обрабатывает элементы массива с использованием переданной ему функции, а потом преобразует массив в плоскую структуру.
const arr = ["it's Sunny in", "", "California"];
arr.flatMap(x => x.split(" "));
// ["it's","Sunny","in", "", "California"]
Разница между flat
и flatMap
заключается в том, что методу flatMap
можно передать собственную функцию, преобразующую элементы исходного массива. В дополнение к этому, flatMap
, в отличие от flat
, «поднимает» элементы массивов лишь на 1 уровень. Этот метод возвращает новый массив. Он может оказаться полезным в тех случаях, когда, перед тем, как сделать некий массив «плоским», нужно как-то обработать его элементы.
Новые возможности JS, находящиеся на 3 этапе согласования
На третьем этапе согласования находится немало новых интересных предложений по расширению и улучшению языка. Рассмотрим некоторые из них.
▍Разделители разрядов чисел
Бывало у вас такое: записываете в переменную длинное число и сомневаетесь в его правильном написании? Предложение, о котором идёт речь, позволяет разделять разряды чисел символами подчёркивания. Это упрощает работу с числами.
1_000_000_000 // Так, это миллиард
101_475_938.38 // А тут у нас - сто миллионов с небольшим
let fee = 123_00; // $123 (здесь, видимо, 12300 центов)
let fee = 12_300; // $12,300 (ну и пошлина!)
let amount = 12345_00; // 12,345 (тут, скорее всего, 1234500 центов)
let amount = 123_45.00; // 12345 (видимо, какая-то денежная величина)
let amount = 1_234_500; // 1,234,500
let budget = 1_000_000_000_000;
// Что записано `budget`? Это - 1 триллион!
//
// Проверим:
console.log(budget === 10 ** 12); // true
Каждый разработчик, после принятия этой возможности, сам решит — использовать разделители разрядов или нет. Но одно можно сказать точно: эта возможность способна уменьшить неудобства, связанные с подсчётом разрядов больших чисел.
▍Использование await на верхнем уровне кода
Использование ключевого слова await
на верхнем уровне кода позволяет модулям действовать в роли больших асинхронных функций. Благодаря этой возможности ECMAScript-модули могут ожидать появления неких ресурсов. Это приводит к тому, что другие модули, импортирующие их, будут ждать момента готовности к работе тела импортированных модулей.
Причина появления этой возможности заключается в том, что когда импортируется модуль, имеющий функцию, объявленную с ключевым словом async
, выход этой функции будет равняться undefined
.
В следующем примере показаны два файла. В output
может попасть undefined
в том случае, если функция вызвана до завершения работы задач, представленных промисами.
// awaiting.mjs
import { process } from "./some-module.mjs";
const dynamic = import(computedModuleSpecifier);
const data = fetch(url);
export const output = process((await dynamic).default, await data);
// usage.mjs
import { output } from "./awaiting.mjs";
export function outputPlusValue(value) { return output + value }
console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);
В файле usage.mjs
всё остановится до тех пор, пока в awaiting.js
не будут разрешены промисы.
▍Оператор ?? и проверка значений только на null и undefined
Возможно, среди всех Stage 3-предложений, это — самое полезное. Нам часто приходится писать примерно такой код:
const obj = {
name: 'James'
};
const name = obj.name || 'Jane'; // James
Если obj.name
представлено неким ложным значением, тогда в name
попадает строка Jane
. В результате в name
не окажется нечто вроде undefined
. Но проблема заключается в том, что и пустая строка в этом случае будет воспринята как ложное значение. Если это учесть — данный код надо переписать так:
const name = (obj.name && obj.name !== '') || 'Jane';
Неудобно постоянно так писать. Оператор ??
(два вопросительных знака) позволяет проверять значения только на null
и undefined
:
const response = {
settings: {
nullValue: null,
height: 400,
animationDuration: 0,
headerText: '',
showSplashScreen: false
}
};
const undefinedValue = response.settings.undefinedValue ?? 'some other default'; // результат: 'some other default'
const nullValue = response.settings.nullValue ?? 'some other default'; // результат: 'some other default'
const headerText = response.settings.headerText ?? 'Hello, world!'; // результат: ''
const animationDuration = response.settings.animationDuration ?? 300; // результат: 0
const showSplashScreen = response.settings.showSplashScreen ?? true; // результат: false
▍Оператор?.. и опциональные цепочки
Это предложение близко к только что рассмотренному, объединяющему проверки на null
и undefined
. Известно, что этой возможностью интересуются пользователи в TypeScript.
Рассмотрим пример:
const city = country && country.city;
// undefined если city не существует
Для того чтобы добраться до свойства city
объекта country
, нужно проверить существование объекта country
и существование в нём свойства city
.
Благодаря использованию оператора ?.
(вопросительный знак и точка) этот код можно преобразовать так:
const city = country?.city; // undefined если city не существует
Эта возможность кажется полезной и в таких ситуациях:
import { fetch } from '../yourFetch.js';
(async () => {
const res = await fetch();
// res && res.data && res.data.cities || undefined
const cities = res?.data?.cities;
})();
▍Метод Promise.any
Метод Promise.any
принимает итерируемый объект, содержащий promise-объекты, и возвращает промис, который успешно разрешается тогда, когда успешно разрешится хотя бы один из переданных ему promise-объектов. Если же все promise-объекты окажутся отклонёнными — он возвращает массив, содержащий сведения о причинах их отклонения.
Вот как выглядит использование Promise.any
с конструкцией async/await:
try {
const first = await Promise.any(promises);
// Любой из промисов был успешно разрешён.
} catch (error) {
// Все промисы были отклонены.
}
Вот — то же самое, реализованное с использованием промисов:
Promise.any(promises).then(
(first) => {
// Любой из промисов был успешно разрешён.
},
(error) => {
// Все промисы были отклонены.
}
);
В JavaScript уже имеются методы Promise.all
, .allSettled
, .race
, а вот метода, подобного .any
, не было. В результате перед нами — новая возможность, которая дополняет существующие и может оказаться полезной в определённых ситуациях. Несмотря на то, что это предложение уже находится на третьей стадии согласования, оно, возможно, не попадёт в следующую редакцию стандарта ECMAScript так как нуждается в дополнительных испытаниях.
Итоги
Существует множество интересных предложений по развитию JavaScript, находящихся на третьей стадии согласования. Интересно будет увидеть их в стандартах ES11 и ES12. Конечно, вряд ли кто-то будет пользоваться ими всеми, но некоторые из них, определённо, найдут широкое применение и будут способствовать росту качества JS-кода.
Уважаемые читатели! Пользуетесь ли вы уже какими-нибудь возможностями JavaScript, которые почти готовы для включения их в следующую версию стандарта?
Автор: ru_vds