Що нового у JavaScript: підсумки Google I/O 2019

10 хв. читання

Регулярні вирази: ретроспективна перевірка

Регулярні вирази (RegEx або RegExp) — потужна фіча майже будь-якої мови програмування. Коли справа доходить до пошуку підрядка, що відповідає складному шаблону, саме вони стають у пригоді. Досі регулярні вирази у JS мали все необхідне, окрім ретроспективної перевірки.

Випереджальна перевірка

Для початку розберемося, що таке випереджальна перевірка в регулярних виразах (яку JS вже реалізовує). Випереджальна перевірка в регулярних виразах дозволяє знайти в рядку підрядок, який може стояти, а може і не стояти попереду іншого визначеного підрядка . Наприклад, щоб серед слів MangoJuice, VanillaShake, GrapeJuice знайти ті, в яких є Juice, можемо застосувати синтаксис позитивної випереджальної перевірки.

Існує два види випереджальної перевірки: позитивна та негативна.

Позитивна випереджальна перевірка

Позитивна випереджальна перевірка використовується, щоб знайти підрядок, що стоїть перед іншим, відомим нам, підрядком. Синтаксис буде таким:

/[a-zA-Z]+(?=Juice)/

Вище ми знаходимо слова з літерами будь-якого регістру, після яких стоїть Juice. Не варто плутати з групуванням у RegExp. Синтаксис випереджальної та ретроспективної перевірок також містить лапки, проте вони не зберігають підрядок. Поглянемо, який вигляд матиме позитивна випереджальна перевірка у коді:

const testString = "MangoJuice, VanillaShake, GrapeJuice";
const testRegExp = /[a-zA-Z]+(?=Juice)/g;
const matches = testString.match( testRegExp );
console.log( matches ); // ["Mango", "Grape"]

Негативна випереджальна перевірка

Результат негативної випереджальної перевірки повністю протилежний. Тобто ми вибираємо всі слова, які не закінчуються на Juice. Синтаксис негативної випереджальної перевірки майже ідентичний, різниця лише в тому, що в позитивній перевірці ми використовували знак =, а тут – !.

/[a-zA-Z]+(?!Juice)/

Наведений шаблон знайде всі слова, у яких немає Juice. Однак як результат ми отримаємо всі слова в рядку, адже всі вони не закінчуються словом Juice. Нам треба бути точнішими:

/(Mango|Vanilla|Grape)(?!Juice)/

Шаблон виділить Mango, Vanilla і Grape як слова без Juice. Спробуймо на практиці:

const testString = "MangoJuice, VanillaShake, GrapeJuice";
const testRegExp = /(Mango|Vanilla|Grape)(?!Juice)/g;
const matches = testString.match( testRegExp );
console.log( matches ); // ["Vanilla"]

Ретроспективна перевірка

Подібно до випереджальної перевірки, ретроспективна перевірка — ще один вид синтаксису в RegExp, щоб отримати підрядок, який стоїть (або не стоїть) позаду підрядка, що відповідає шаблону. Наприклад, ми перевіряємо рядок FrozenBananas, DriedApples, FrozenFish. Ми можемо використати позитивну ретроспективну перевірку, щоб знайти слова, у яких є Frozen на початку.

Як можна здогадатися, ретроспективна перевірка поділяється на позитивну та негативну.

Позитивна ретроспективна перевірка

Позитивна ретроспективна перевірка допомагає знайти підрядок, після якого стоїть інший підрядок, що відповідає визначеному шаблону. Синтаксис позитивної ретроспективної перевірки:

/(?<=Frozen)[a-zA-Z]+/

Можна помітити схожість з випереджальною перевіркою — тільки з різницею у символі <, який позначає те, що ми шукаємо відповідності на початку. Наведений шаблон виділить усі слова, які починаються на Frozen або перед якими є Frozen. На практиці:

const testString = "FrozenBananas, DriedApples, FrozenFish";
const testRegExp = /(?<=Frozen)[a-zA-Z]+/g;
const matches = testString.match( testRegExp );
console.log( matches ); // ["Bananas", "Fish"]

Негативна ретроспективна перевірка

Негативна ретроспективна перевірка, відповідно, виділяє підрядок, коли перед ним немає підрядка, що відповідає шаблону. Наприклад, якщо серед слів FrozenBananas, DriedApples, FrozenFish потрібно було б обрати лише ті, що не стоять поруч з Frozen, ми могли б використати такий синтаксис:

/(?<!Frozen)[a-zA-Z]+/

Але такий шаблон виділить усі слова у рядку як ті, щоне починаються з Frozen. Тож задамо шаблон точніше:

/(?<!Frozen)(Bananas|Apples|Fish)/

Який вигляд все матиме у коді:

const testString = "FrozenBananas, DriedApples, FrozenFish";
const testRegExp = /(?<!Frozen)(Bananas|Apples|Fish)/g;
const matches = testString.match( testRegExp );
console.log( matches ); // ["Apples"]

Підтримка:

Поля класу

Поля класу — новий синтаксис для визначення властивостей об'єкта поза конструктором класу. Існує два типи полів класу: публічні та приватні.

Публічні поля класу

Досі властивість об'єкта треба було оголошувати всередині конструктора класу. До таких властивостей можна отримати доступ в екземплярі класу (об'єкті).

class Dog {
    constructor() {
        this.name = 'Tommy';
    }
}

Якщо у нас є клас, який наслідує батьківський клас, треба було обов'язково викликати super() з конструктора дочірнього класу, перед тим як дати йому власні властивості:

class Animal {}
class Dog extends Animal {
    constructor() {
        super(); // викликаємо super перед використанням `this` у конструкторі
        this.sound = 'Woof! Woof!';
    }
    makeSound() {
        console.log( this.sound );
    }
}
// створюємо екземпляр
const tommy = new Dog();
tommy.makeSound(); // Woof! Woof!

З синтаксисом публічних полів класу ми можемо оголосити поля класу поза конструктором, JavaScript зробить неявний виклик super().

class Animal {}
class Dog extends Animal {
    sound = 'Woof! Woof!'; // публічне поле класу
    makeSound() {
        console.log( this.sound );
    }
}
// створюємо екземпляр
const tommy = new Dog();
tommy.makeSound(); // Woof! Woof!

Коли JavaScript неявно викликає super(), він передає усі аргументи, визначені при створенні екземпляру (що є стандартною поведінкою в JavaScript, яка не має нічого спільного з приватними полями класу). Отже, якщо батьківському конструктору потрібні спеціальні аргументи, не забудьте викликати super() вручну.

class Animal {
    constructor( ...args ) {
        console.log( 'Animal args:', args );
    }
}
class Dog extends Animal {
    sound = 'Woof! Woof!'; // публічне поле класу 
makeSound() {
        console.log( this.sound );
    }
}
// створюємо екземпляр
const tommy = new Dog( 'Tommy', 'Loves', 'Toys!' );
tommy.makeSound(); // Animal args: [ 'Tommy', 'Loves', 'Toys!' ]

Підтримка:

Приватні поля класу

Як ми знаємо, в JavaScript немає модифікаторів доступу, на зразок public, private та protected. Усі властивості об'єкта є публічними за замовчуванням, тобто кожен може отримати до них доступ. Найбільш наближений спосіб зробити поля класу прихованими, зробити назву властивості типу Symbol. Можливо, ви використовували префікс _, щоб виокремити властивості, які повинні бути приватними, але це лише позначення, яке не розв'язує проблему загалом.

З приватними полями класу ми можемо зробити властивості класу доступними лише всередині класу та приховати їх від екземпляра (об'єкта). Розглянемо все на попередньому прикладі:

class Dog {
    _sound = 'Woof! Woof!'; // позначаємо приватне поле
    
    makeSound() {
        console.log( this._sound );
    }
}
// create instance
const tommy = new Dog();
console.log( tommy._sound ); // Woof! Woof!

З префіксом _ ми не розв'язуємо нашу проблему. Тепер ми можемо визначити приватну властивість класу, додавши префікс #. Якщо після цього спробувати отримати доступ до приватної властивості об'єкта, отримаємо SyntaxError: Undefined private field.

class Dog {
    #sound = 'Woof! Woof!'; // це приватне поле
    makeSound() {
        console.log( this.#sound );
    }
}
// створюємо екземпляр
const tommy = new Dog();
tommy.makeSound() // Woof! Woof!
//console.log( tommy.#sound ); // SyntaxError

Приватні властивості доступні лише всередині класу, де вони визначені. Отже, вони не будуть доступні всередині батьківського чи дочірнього класу.

Ми можемо визначити приватні (і публічні) властивості, не проініціалізувавши їх, тобто вони будуть undefined.

class Dog {
    #name;
    constructor( name ) {
        this.#name = name;
    }
    showName() {
        console.log( this.#name );
    }
}
// ствоємо екземпляр
const tommy = new Dog( 'Tommy' );
tommy.showName(); // Tommy

Підтримка:

string.matchAll

У типу даних string є метод прототипу match, який повертає підрядки, що відповідають певному заданому RegExp шаблону або ключовому слову.

const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /([A-Z0-9]+)/g;
console.log( colors.match( matchColorRegExp ) );
// Вивід:
["EEE", "CCC", "FAFAFA", "F00", "000"]

Однак так ми не отримуємо іншу додаткову інформацію, на зразок індексу кожного збігу в рядку. Якщо ми заберемо прапор g, то все ще зможемо отримати додаткову інформацію, та лише про перший знайдений збіг.

const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /#([A-Z0-9]+)/;
console.log( colors.match( matchColorRegExp ) );
// Вивід: (результат скорочено для наочності)
["#EEE", "EEE", index: 0, input: "<colors>"]

Наприкінці нам треба використати метод .exec з об'єктом RegExp, синтаксис тут буде досить складним. Обходимо всі результати в циклі while, поки exec не поверне null. Зверніть увагу, exec не повертає ітератор.

const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /#([A-Z0-9]+)/g;
// strict mode,
// Uncaught ReferenceError: match is not defined
while( match = matchColorRegExp.exec( colors ) ) {
  console.log( match );
}
// Вивід: (результат скорочено для наочності)
["#EEE", "EEE", index: 0, input: "<colors>"]
["#CCC", "CCC", index: 6, input: "<colors>"]
["#FAFAFA", "FAFAFA", index: 12, input: "<colors>"]
["#F00", "F00", index: 21, input: input: "<colors>"]
["#000", "000", index: 27, input: input: "<colors>"]

Тут на допомогу приходить метод matchAll, який повертає ітератор, і кожен виклик next() послідовно повертає наступний елемент, що відповідає шаблону.

const colors = "#EEE, #CCC, #FAFAFA, #F00, #000";
const matchColorRegExp = /#([A-Z0-9]+)/g;
console.log( ...colors.matchAll( matchColorRegExp ) );
// Вивід: (результат скорочено для наочності)
["#EEE", "EEE", index: 0, input: "<colors>"]
["#CCC", "CCC", index: 6, input: "<colors>"]
["#FAFAFA", "FAFAFA", index: 12, input: "<colors>"]
["#F00", "F00", index: 21, input: input: "<colors>"]
["#000", "000", index: 27, input: input: "<colors>"]

Підтримка:

Іменовані лапкові групи

Концепція лапкових груп реалізована в JavaScript трохи інакше, ніж в інших мовах. Якщо ми розміщуємо регулярний вираз всередині лапок (за винятком випереджальної та ретроспективної перевірок), він перетворюється на лапкову групу і всі підрядки, що відповідають шаблону, відображатимуться всередині знайденого елемента.

Звернемося до попереднього прикладу, де перший елемент масиву — повна відповідність шаблону, а другий елемент — значення, що відповідає лапковій групі.

["#EEE", "EEE", index: 0, input: "<colors>"]

Якщо є декілька лапкових груп, вони будуть виведені послідовно. Розглянемо на прикладі:

const str = "My name is John Doe.";
const matchRegExp = /My name is ([a-z]+) ([a-z]+)/i;
const result = str.match( matchRegExp );console.log( result );
// помилка, якщо результат null
console.log( { firstName: result[1], lastName: result[2] } );
// Вивід:
["My name is John Doe", "John", "Doe", index: 0, input: "My name is John Doe.", groups: undefined]
{firstName: "John", lastName: "Doe"}

Як можна побачити, перший елемент — це повний рядок, який відповідає шаблону, а другий та третій елементи — результати, які відповідають лапковій групі.

З використанням іменованих лапкових груп ми можемо зберегти окремі результати всередині об'єкта groups з мітками. Синтаксис для оголошення іменованої групи буде таким: (?<labelName>$pattern).

const str = "My name is John Doe.";
const matchRegExp = /My name is (?<firstName>[a-z]+) (?<lastName>[a-z]+)/i;
const result = str.match( matchRegExp );
console.log( result );
console.log( result.groups );
// Вивід:
["My name is John Doe", "John", "Doe", index: 0, input: "My name is John Doe.", groups: {firstName: "John", lastName: "Doe"}]
{firstName: "John", lastName: "Doe"}

Іменовані лапкові групи також працюють з методом matchAll.

Підтримка:

Числові розділювачі

Коли справа доходить до великих чисел, читабельність стає проблемою. Наприклад, щоб дізнатися, що це за число: 1000000000, необхідно порахувати нулі, а це може дійсно засмучувати.

В новіших версіях JavaScript, ми можемо використовувати розділювач _, який допомагає виокремити частини числа для досягнення читабельності:

var billion = 1_000_000_000;
console.log( billion ); // 1000000000

Ми можемо розмістити _ у будь-якій частині числа і JavaScript просто проігнорує символ. Така фіча працюватиме з будь-якими типами чисел: цілими, з плаваючою комою, у двійковому, шістнадцятковому чи вісімковому форматі.

console.log( 1_000_000_000.11 ); // 1000000000.11
console.log( 1_000_000_000.1_012 ); // 1000000000.1012
console.log( 0xFF_00_FF ); // 16711935
console.log( 0b1001_0011 ); // 147
console.log( 0o11_17 ); // 591

Підтримка:

BigInt

Тип number в JavaScript створюється функцією Number (яка також є конструктором). Максимальне значення number(2⁵³ - 1), тобто 9007199254740991. Ми також можемо отримати це число, використавши властивість Number.MAX_SAFE_INTEGER.

Коли ми використовуємо числовий літерал, JavaScript огортає його конструктором Number, щоб створити об'єкт з відповідними методами прототипу. Такий процес відбувається з усіма примітивними типами даних. Більше про концепцію Примітивів vs Об'єктів — за посиланням.

Що трапиться, якщо ми збільшимо отримане число?

console.log( Number.MAX_SAFE_INTEGER ); // 9007199254740991
console.log( Number.MAX_SAFE_INTEGER + 10 ); // 9007199254741000

Останній лог виводить хибне значення. Усе тому, що JavaScript не може обчислювати значення, котрі перевищують Number.MAX_SAFE_INTEGER.

Тут на допомогу приходить BigInt, який використовується для цілочислових значень, що перевищують Number.MAX_SAFE_INTEGER. Подібно до Number, BigInt поводить себе і як функція, і як конструктор. З появою BigInt JavaScript отримав новий вбудований примітив bigint для представлення великих цілих значень.

var large = BigInt( 9007199254740991 );
console.log( large ); // 9007199254740991n
console.log( typeof large ); // bigint

Аби показати, що число приймає тип BigInt, слід додати наприкінці n. Так ми отримаємо літерал типу BigInt.

З таким типом даних ми можемо безпечно робити математичні операції над великими числами, які також належать до типу даних bigint.

var large = 9007199254740991n;
console.log( large + 10n ); // 9007199254741001n

Типи number та bigint, насправді, відрізняються, оскільки останній працює лише з цілими числами. Тож не дивно, що математичні операції між number та bigint не підтримуються.

Функція BigInt може приймати число будь-якого формату: ціле, двійкове, шістнадцяткове тощо. Внутрішньо все одно проводиться перетворення до основи 10.

BigInt також підтримує числові розділювачі.

var large = 9_007_199_254_741_001n;
console.log( large ); // 9007199254741001n

Підтримка:

Array: flat та flatMap

flat та flatMap — нові методи прототипу Array.

Array.flat

З методом flat(n) ми можемо згладити масив до рівня n та повернути новий масив. За замовчуванням n приймає 1. Ми можемо передати Infinity, щоб згладити масив невідомої вкладеності.

var nums = [1, [2, [3, [4, 5]]]];
console.log( nums.flat() ); // [1, 2, [3, [4,5]]]
console.log( nums.flat(2) ); // [1, 2, 3, [4,5]]
console.log( nums.flat(Infinity) ); // [1, 2, 3, 4, 5]

Підтримка:

Array.flatMap

Імовірно, вам не раз доводилось перетворювати масив методом map, а потім згладжувати його. Розглянемо приклад з піднесенням до квадрата чисел:

var nums = [1, 2, 3];
var squares = nums.map( n => [ n, n*n ] )
console.log( squares ); // [[1,1],[2,4],[3,9]]
console.log( squares.flat() ); // [1, 1, 2, 4, 3, 9]

Замість такого громіздкого коду ми можемо використовувати метод flatMap, який одночасно робить певні дії над кожним елементом масиву і згладжує його. Тут ми можемо згладити лише один рівень вкладеності.

var nums = [1, 2, 3];
var makeSquare = n => [ n, n*n ];
console.log( nums.flatMap( makeSquare ) ); // [1, 1, 2, 4, 3, 9]

Підтримка:

Object: fromEntries

Ми можемо отримати пари «ключ-значення» об'єкта, використовуючи статичний метод entries, який повертає масив, де кожен елемент буде також масивом (його перший елемент — ключ об'єкта, а другий — значення).

var obj = { x: 1, y: 2, z: 3 };
var objEntries = Object.entries( obj );
console.log( objEntries ); // [["x", 1],["y", 2],["z", 3]]

З появою статичного методу fromEntries ми можемо зробити абсолютно протилежну операцію: перетворити отриманий масив на об'єкт.

var entries = [["x", 1],["y", 2],["z", 3]];
var obj = Object.fromEntries( entries );
console.log( obj ); // {x: 1, y: 2, z: 3}

Ми використовували entries, щоб спростити фільтрацію та перетворення кожного елемента масиву. Але досі перетворення отриманого масиву назад в об'єкт спричиняло багато труднощів. Тут на допомогу приходить метод fromEntries.

var obj = { x: 1, y: 2, z: 3 };
// [["x", 1],["y", 2],["z", 3]]
var objEntries = Object.entries( obj );
// [["x", 1],["z", 3]]
var filtered = objEntries.filter(
 ( [key, value] ) => value % 2 !== 0 // фільтруємо непарні числа
);
console.log( Object.fromEntries( filtered ) ); // {x: 1, z: 3}

Коли для зберігання пар «ключ-значення» в порядку додавання ми використовуємо Map, внутрішня структура даних нагадує формат entries. Тож ми також можемо використати fromEntries, щоб легко створити об'єкт з Map.

var m = new Map([["x", 1],["y", 2],["z", 3]]);
console.log( m ); // {"x" => 1, "y" => 2, "z" => 3}
console.log( Object.fromEntries( m ) ); // {x: 1, y: 2, z: 3}

Підтримка:

Глобальний this

this у JavaScript не має визначеного значення. Воно залежить від контексту, в якому ми його використовуємо. В будь-якому середовищі this вказує на глобальний об'єкт, якщо ми отримуємо доступ з глобальної області програми.

Наприклад, у JavaScript, глобальним this є об'єкт window, що можна перевірити, додавши console.log(this) на початок JS-файла або в консолі браузера.

Що нового у JavaScript: підсумки Google I/O 2019

Глобальне значення this всередині Node.js вказує на об'єкт global, а всередині веб-воркера — безпосередньо на веб-воркер. Проте отримати глобальне значення this не завжди так просто. Наприклад, this в конструкторі вказує на екземпляр класу.

Отже, різні середовища передбачають ключові слова, на зразок self, для позначення глобального this. Node.js використовує global, JavaScript та Web Workers — this. Перевіряючи альтернативні ключові слова, ми можемо створити функцію, що повертатиме нам потрібне глобальне значення this.

const getGlobalThis = () => {
 if (typeof self !== 'undefined') return self;
 if (typeof window !== 'undefined') return window;
 if (typeof global !== 'undefined') return global;
 if (typeof this !== 'undefined') return this;
 throw new Error('Unable to locate global `this`');
};
var globalThis = getGlobalThis();

Проте це не найкраще рішення, як пояснено у статті про глобальний this. Саме тому в JavaScript було передбачено ключове слово globalThis. Тепер не проблема отримати глобальне значення this з будь-якого місця програми:

var obj = { fn: function() {
  console.log( 'this', this === obj ); // true
  console.log( 'globalThis', globalThis === window ); // true
} };
obj.fn();

Підтримка:

Стабільне сортування

ECMAScript не пропонує алгоритм для сортування масивів в рушії JS, передбачається лише синтаксис для цього. Отже, залежно від браузера чи JavaScript-рушія, ви можете отримати різницю в продуктивності сортування і/або стабільності сортування.

Однак тепер ECMAScript реалізовує стабільне сортування масиву. Якщо коротко, то алгоритм сортування вважається стабільним, якщо результатом сортування є змінений масив, який зберігає відносний порядок елементів, які не були відсортовані. Тобто зберігається той порядок, в якому елементи були додані спочатку. Більше про стабільне сортування — за посиланням. Розглянемо приклад:

var list = [
  { name: 'Anna', age: 21 },
  { name: 'Barbra', age: 25 },
  { name: 'Zoe', age: 18 },
  { name: 'Natasha', age: 25 }
];
// сортування в спадному порядку
list.sort( ( a, b ) => b.age - a.age );
// можливий результат
[
  { name: 'Natasha', age: 25 }
  { name: 'Barbra', age: 25 },
  { name: 'Anna', age: 21 },
  { name: 'Zoe', age: 18 },
]

В наведеному прикладі в масиві list об'єкт з полем name: Barbra передує об'єкту з name: Natasha. Оскільки поле age цих об'єктів однакове, ми могли б очікувати, що порядок елементів після сортування залишиться незмінним. Однак це не завжди так. Результат алгоритму сортування залежить від JavaScript-рушія, який ви використовуєте.

Проте зараз усі сучасні браузери та Node.js використовують стабільне сортування за замовчуванням з методом sort.

// результат стабільного сортування
[
  { name: 'Barbra', age: 25 },
  { name: 'Natasha', age: 25 }
  { name: 'Anna', age: 21 },
  { name: 'Zoe', age: 18 },
]

Деякі JavaScript-рушії й раніше підтримували алгоритм стабільного сортування, однак лише для невеликих масивів. Щоб збільшити продуктивність для великих масивів, зазвичай нехтували впорядженістю елементів.

Підтримка:

  • Node: 12+
  • Chrome: 70+
  • Firefox: 62+

API Інтернаціоналізації

API Інтернаціоналізації передбачається стандартом ECMAScript для форматування чисел, рядків, дати та часу в певних мовах. API доступний через об'єкт Intl. За допомогою конструктора створюється форматер даних, особливий для певної мови. Ви можете знайти список підтримуваних мов за посиланням.

Intl.RelativeTimeFormat

Нерідко нам потрібно працювати з часом у відносному форматі, на зразок 5 хвилин тому, вчора, 1 тиждень тому тощо. Якщо ваш веб-сайт підтримує багато мов, вам необхідно зберігати усі можливі комбінації відносного часу.

JavaScript передбачив конструктор RelativeTimeFormat(locale, config) для об'єкта Intl, який дає змогу створити форматер часу для певної мови. Так створюється об'єкт з методом прототипу format(value, unit) для генерації форматів часу.

// español (іспанська)
var rtfEspanol= new Intl.RelativeTimeFormat('es', {
  numeric: 'auto'
});
log( rtfEspanol.format( 5, 'day' ) ); // dentro de 5 días
log( rtfEspanol.format( -5, 'day' ) ); // hace 5 días
log( rtfEspanol.format( 15, 'minute' ) ); // dentro de 15 minutos

Підтримка:

Intl.ListFormat

ListFormat API дає можливість об'єднувати елементи в списку у форматі and чи or. Наприклад, [apples, mangoes, bananas] можна представити як apples, mangoes and bananas або apples, mangoes or bananas відповідно.

Спершу необхідно створити екземпляр форматера конструктором ListFormat(locale, config) та використати метод прототипу format(list), щоб згенерувати формат списку для відповідної мови.

// español (іспанська)
var lfEspanol = new Intl.ListFormat('es', {
  type: 'disjunction'
});
var list = [ 'manzanas', 'mangos', 'plátanos' ];
log( lfEspanol.format( list ) ); // manzanas, mangos o plátanos

Підтримка:

Intl.Locale

Іноді доводиться працювати з більш специфічними даними, які залежать від мови. Конструктор Intl.Locale(localeId, config) генерує форматований рядок відповідно до мови, вказаної в config. Згенерований об'єкт містить метод прототипа toString для отримання відформатованого рядка певної мови.

const krLocale = new Intl.Locale( 'ko', {
  script: 'Kore', region: 'KR',
  hourCycle: 'h12', calendar: 'gregory'
} );
log( krLocale.baseName ); // ko-Kore-KR
log( krLocale.toString() ); // ko-Kore-KR-u-ca-gregory-hc-h12

Більше інформації про локальні ідентифікатори та теги локалі Unicode за посиланням.

Підтримка:

Проміси

Досі конструктор Promise пропонував статичні методи all та race. Promise.all([...promises]) повертає проміс, який ресолвиться після того, як всі передані проміси ресолвляться, та відхиляється, якщо відхиляється хоча б один з них. Promise.race([...promises]) повертає проміс, який ресолвиться після того, як хоча б один вхідний проміс ресолвиться, та відхиляється, якщо хоча б один з промісів відхиляється.

Однак не вистачало статичного метода, який повертав би проміс, який ресолвиться після завершення всіх промісів (як resolve, так і reject). Нам також потрібен був метод, подібний до race, який чекав би, поки хоча б один з промісів заресолвиться.

Promise.allSettled

Метод Promise.allSettled приймає масив промісів та ресолвиться, коли всі проміси завершені (як resolve, так і reject). Проміс, повернений цим методом, не потребує catch-колбеку, оскільки він завжди буде ресолвитись. Колбек then приймає status та value кожного проміса в порядку їх появи:

var p1 = () => new Promise(
  (resolve, reject) => setTimeout( () => resolve( 'val1' ), 2000 )
);
 
var p2 = () => new Promise(
  (resolve, reject) => setTimeout( () => resolve( 'val2' ), 2000 )
); 
 
var p3 = () => new Promise(
  (resolve, reject) => setTimeout( () => reject( 'err3' ), 2000 )
);
var p = Promise.allSettled( [p1(), p2(), p3()] ).then(
  ( values ) => console.log( values )
);
// Output
[ {status: "fulfilled", value: "val1"}
  {status: "fulfilled", value: "val2"}
  {status: "rejected", value: "err3"}
]

Підтримка:

Promise.any

Метод Promise.any подібний до Promise.race, однак проміс, повернений методом, не виконує блок catch, як тільки відхиляється будь-який проміс. Натомість він чекає поки заресолвиться будь-який проміс. Якщо ж жоден проміс не викликав resolve, виконається блок catch. Якщо будь-який проміс заресолвився першим, виконується блок then.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.9K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація