[Перевод] Новшества ES2020, которые мне очень нравятся

В последние годы JavaScript развивается очень быстро. Особенно это характерно для периода, следующего за выходом стандарта ES6 в 2015 году. С тех пор в языке появилось множество замечательных возможностей. Немало нового было предложено и для включения в стандарт ES2020.

n04x8qtegutrdikwlzux8uv3phw.png

Уже сформирован окончательный список возможностей, появления которых можно ожидать в стандарте после его утверждения. Это — хорошая новость для всех любителей JS. Автор статьи, перевод которой мы сегодня публикуем, говорит, что среди этих возможностей есть такие, которым он особенно рад. До их появления ему, в тех ситуациях, в которых они применимы, было гораздо тяжелее писать код. По его словам, появись они в языке раньше, они сэкономили бы ему много времени и сил.

Опциональные цепочки


Опциональные цепочки (Optional Chaining) — это, лично для меня, одна из самых восхитительных возможностей стандарта ES2020. Я написал множество программ, в которых эта возможность оказалась бы крайне полезной. 

Опциональные цепочки позволяют организовать безопасный доступ к глубоко вложенным свойствам объектов без необходимости проверки существования каждого из них. Взглянем на то, как именно работает эта возможность.

Сначала посмотрим на код, который приходилось писать до появления опциональных цепочек.

▍Код до появления опциональных цепочек

const user = {
   firstName:"Joseph",
   lastName:"Kuruvilla",
   age:38,
   address:{
      number:"239",
      street:"Ludwig Lane",
      city:"Chennai",
      zip:"600028",
   prop1:{
    prop2:{
     prop3:{
      prop4:{
       value:'sample'
      }
     }
    }
   }
   }
}
if(user && user.address){
 console.log(user.address.zip);
 //600028
}
if(user && user.address && user.address.prop1 && user.address.prop1.prop2 && user.address.prop1.prop2.prop3 && user.address.prop1.prop2.prop3.prop4){
 console.log(user.address.prop1.prop2.prop3.prop4.value);
 //sample
}
//Попытка доступа к несуществующему свойству
console.log(user.address.prop102.po);
//Error


Как видите, для того чтобы избежать возникновения ошибок, вроде Cannot read property 'po' of undefined, нужно, на каждом уровне вложенности, проверять свойства на предмет их существования. При увеличении глубины вложенности сущностей растёт и количество проверяемых свойств. Это означает, что программисту приходится самому писать код, который защищает его от свойств, при обращении к которым можно столкнуться со значениями null или undefined.

▍Код после появления опциональных цепочек


Писать код, подобный тому, который мы только что рассмотрели, с появлением опциональных цепочек стало гораздо легче. Для организации безопасной работы с глубоко вложенными свойствами объектов достаточно воспользоваться оператором ?.. Он избавляет нас от необходимости самостоятельной проверки значений на null и undefined.

Вот как это выглядит:

const user = {
   firstName:"Joseph",
   lastName:"Kuruvilla",
   age:38,
   address:{
      number:"239",
      street:"Ludwig Lane",
      city:"Chennai",
      zip:"600028",
   prop1:{
    prop2:{
     prop3:{
      prop4:{
       value:'sample'
      }
     }
    }
   }
   }
}
console.log(user?.address?.zip);
//600028
console.log(user?.address?.prop1?.prop2?.prop3?.prop4?.value);
//sample
//Попытка доступа к несуществующему свойству
console.log(user?.address?.prop102?.po);
//undefined


Ну не прекрасно ли это? Благодаря этому новшеству ES2020 стало возможным избавление от огромного количества строк кода.

Проверка значений только на null и undefined


Проверка значений только на null и undefined (Nullish Coalescing) — это одна из тех возможностей, которые прямо-таки восхитили меня ещё тогда, когда возможности были на стадии предложений. Я часто сталкивался с необходимостью писать специализированные функции для выполнения соответствующих проверок.

Известно, что в JavaScript существуют «ложные» и «истинные» значения. Теперь можно сказать, что к ним добавились и «нулевые» значения. В состав таких значений входят null и undefined. С точки зрения JavaScript «ложными» являются пустые строки, число 0, значения undefined, null, false, NaN. То есть, например, некое выражение для проверки значения на «ложность» сработает и на пустой строке, и на значении undefined, и много ещё на чём. А выражение для проверки значения на предмет того, является ли оно «нулевым», вернёт true только для null и undefined. Может, лично вам эта возможность не кажется такой уж замечательной, но, на самом деле, она очень и очень важна.

Рассмотрим примеры.

▍Код до появления возможности проверки значений только на null и undefined


Недавно я работал над проектом, в котором мне нужно было реализовать функционал переключения между светлой и тёмной темами. Мне нужно было при этом проверять состояние элемента управления, узнавать, соответствует ли оно значению true или false. Если же пользователь не устанавливал никакого значения, оно, по умолчанию, должно было равняться true. Вот как я решал эту задачу до появления возможности проверки значений только на null и undefined:

const darkModePreference1 = true
const darkModePreference2 = false
const darkModePreference3 = undefined
const darkModePreference4 = null
const getUserDarkModePreference = (darkModePreference) => {
  if (darkModePreference || darkModePreference === false) {
    return darkModePreference
  }
  return true
}
getUserDarkModePreference(darkModePreference1) 
// true
getUserDarkModePreference(darkModePreference2) 
// false
getUserDarkModePreference(darkModePreference3) 
// true
getUserDarkModePreference(darkModePreference4) 
// true


▍Код после появления возможности проверки значений только на null и undefined


После того, как эта возможность появилась в языке, для проверки на null и undefined достаточно оператора ??. При этом можно обойтись без условного оператора if:

const darkModePreference1 = true
const darkModePreference2 = false
const darkModePreference3 = undefined
const darkModePreference4 = null
const getUserDarkModePreference = (darkModePreference) => {
  return darkModePreference ?? true;
}
getUserDarkModePreference(darkModePreference1) 
// true
getUserDarkModePreference(darkModePreference2) 
// false
getUserDarkModePreference(darkModePreference3) 
// true
getUserDarkModePreference(darkModePreference4) 
// true


Здесь происходит следующее: если переменная darkModePreference содержит «нулевое» значение, тогда возвращается значение true. Благодаря этому упрощается написание кода, он оказывается компактным и лёгким для понимания.

Динамические импорты


Динамические импорты (Dynamic Imports) способствуют более эффективной работе приложений. Эта технология позволяет импортировать JS-файлы динамически, в виде модулей, без привлечения дополнительных инструментов. При этом, если модуль не нужен, то он не импортируется. До ES2020 модули импортировались вне зависимости от того, используются ли они приложением или нет.

Например, предположим, что нам нужно реализовать функционал загрузки некоего файла в формате PDF.

Рассмотрим, как обычно, старый и новый варианты решения этой задачи.

▍Код до появления поддержки динамических импортов


Исходя из реального положения дел, можно ожидать того, что возможность загрузки материалов в формате PDF будет использоваться не всеми посетителями страницы. Но соответствующий модуль, всё равно, нужно импортировать в код. Это означает, что модуль, нужен он или нет, будет загружен при загрузке страницы.

import { exportAsPdf } from './export-as-pdf.js'
const exportPdfButton = document.querySelector('.exportPdfBtn');
exportPdfButton.addEventListener('click', exportAsPdf);


Это создаёт дополнительную нагрузку на системы, которую можно облегчить, воспользовавшись ленивой загрузкой модулей. Сделать это можно с помощью технологии разделения кода, которая доступна в webpack или в других загрузчиках модулей (а их использование и само по себе означает трату некоего количества системных ресурсов).

Но теперь, благодаря ES2020, у нас есть стандартный способ динамической загрузки модулей, который позволяет обойтись без бандлеров.

▍Код после появления поддержки динамических импортов

const exportPdfButton = document.querySelector('.exportPdfBtn');
exportPdfButton.addEventListener('click', () => {
  import('./export-as-pdf.js')
    .then(module => {
      module.exportAsPdf()
    })
    .catch(err => {
      // если модуль загрузить не удаётся - обработать ошибку
    })
})


Как видите, теперь можно организовать ленивую загрузку модулей, выполняя загрузку только тогда, когда нужен соответствующий модуль. Это снижает нагрузку на системы и увеличивает скорость загрузки страниц.

Конструкция Promise.allSettled


Если вам нужно выполнить некое действие только в том случае, когда все промисы успешно разрешились, вы можете воспользоваться методом Promise.all(). Правда, у этого метода есть один недостаток. Метод выдаст ошибку в том случае, если хотя бы один переданный ему промис окажется отклонённым. Это значит, что необходимое действие не будет выполнено до тех пор, пока все промисы не будут успешно разрешены.

Возможно, вам это не нужно. Возможно, вас устроит следующий сценарий: «Мне не важен результат. Мне нужно, чтобы код был выполнен после завершения работы всех промисов». В таком случае вам пригодится метод Promise.allSettled(). Соответствующий промис успешно разрешается только после того, как завершится работа других промисов. При этом неважно — успешно или неуспешно они отработали.

▍Код, в котором используется конструкция Promise.all

const PromiseArray = [
    Promise.resolve(100),
    Promise.reject(null),
    Promise.resolve("Data release"),
    Promise.reject(new Error('Something went wrong'))];
Promise.all(PromiseArray)
  .then(data => console.log('all resolved! here are the resolve values:', data))
  .catch(err => console.log('got rejected! reason:', err))
//got rejected! reason: null


Как видно, Promise.all выдаёт ошибку после отклонения одного из переданных ему промисов.

▍Код, в котором используется конструкция Promise.allSettled

const PromiseArray = [
    Promise.resolve(100),
    Promise.reject(null),
    Promise.resolve("Data release"),
    Promise.reject(new Error('Something went wrong'))];
Promise.allSettled(PromiseArray).then(res =>{
console.log(res);
}).catch(err => console.log(err));
//[
//{status: "fulfilled", value: 100},
//{status: "rejected", reason: null},
//{status: "fulfilled", value: "Data release"},
//{status: "rejected", reason: Error: Something went wrong ...}
//]


А здесь, хотя некоторые из промисов и отклонены, Promise.allSettled возвращает результаты, выданные всеми переданными ему промисами.

Другие примечательные возможности


▍Тип данных BigInt


Новый тип данных BigInt позволяет работать с числами, длина которых превышает длину чисел, с которыми можно было работать в JavaScript до его появления (pow(2,53)-1). Правда, этот тип данных не является обратно совместимым с тем, что было в языке раньше. Стандарт IEEE 754, на котором основана работа с числами в JavaScript, не поддерживает таких чисел, работа с которыми возможна благодаря BigInt

▍Метод String.prototype.matchAll


Метод String.prototype.matchAll() имеет отношение к регулярным выражениям. Он возвращает итератор, позволяющий работать со всеми совпадениями, найденными в строке с использованием регулярного выражения, включая группы.

▍Глобальное свойство globalThis


Глобальное свойство globalThis хранит ссылку на глобальный объект, соответствующий окружению, в котором выполняется код. В браузере глобальный объект представлен объектом window. В Node.js — это объект global. В веб-воркерах это — объект self.

А какие новшества ES2020 нравятся больше всего вам?

a_bsaactpbr8fltzymtkhqbw1d4.png

© Habrahabr.ru