[Перевод] Ожидаемые новые возможности JavaScript, о которых полезно знать

С момента выхода стандарта ECMAScript 2015 (его ещё называют ES6) JavaScript серьёзно изменился и улучшился. Это очень хорошая новость для всех JS-разработчиков. Более того, теперь новая версия ECMAScript выходит каждый год. Возможно, вы не обратили особого внимания на то, что появилось в самой свежей версии стандарта, который был выпущен в июне 2019 года. Автор заметки, перевод которой мы сегодня публикуем, хочет в двух словах рассказать о новшествах JavaScript, и о том, чего можно ждать в следующей версии стандарта ECMAScript.

fivchddl8mlzwem95dlmcvypba4.png

Здесь будут упомянуты возможности, предложения которых находятся на третьем этапе согласования (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, которые почти готовы для включения их в следующую версию стандарта?

-o2etuqogwhmdnmysb9_vivc9v4.png


1ba550d25e8846ce8805de564da6aa63.png

© Habrahabr.ru