W tym tygodniu ukazała się nowa specyfikacja ECMA-262, czyli nowa specyfikacja JavaScriptu nazwana ES9 / ES2018. Jesteście ciekawi co nowego wnosi do języka? Zapraszam do lektury!

ES6 !== najnowszy JS!

Przeglądając oferty pracy zdarza mi się napotkać informacje w stylu:

Nasz kod piszemy w ES6. Chesz używać najnowszych funkcji języka JavaScript? Przyjdź do nas! bla bla bla...

Za każdym razem, kiedy czytam coś takiego, na mojej twarzy pojawia się lekki uśmiech. Nie jest to broń Boże uśmiech szyderczy ;) Zastanawia mnie tylko dlaczego niektórzy tak beztrosko podchodzą do tematu?

Wydaje mi się, że wynika to z przeskoku jaki dał nam standard ES6. Został on opublikowany w 2015 roku, 6 lat po swoim poprzedniku (ES5). Kolejne standardy ES7, ES8, czy opublikowany ostatnio ES9, pojawiały się rok po roku i wprowadzały mniejszą ilość zmian w stosunku do ES6. Im bardziej szokująca zmiana, tym bardziej ją pamiętamy - tak mi się przyajmniej wydaje. Może dlatego w branży funkcjonuje taki skrót myślowy, że "ES6 = najnowszy JS". Z tym, że nie jest to prawda. To tak samo jak mówienie o SCRUMIE, że jest on procesem...co nie? ;)

Stąd mój apel:

Pracodawco! jeśli rzeczywiście korzystasz z najnowszego standardu ECMAScript to uaktualnij swój opis oferty! Używajmy nazw standardów zgodnie z ich przeznaczeniem. Ten sam apel do wykładowców we wszystkich bootcampach, które są tak popularne ostatnio. Jeśli uczycie ludzi JavaScriptu to napomknijcie chociaż, że aktualnym standardem jest ES* (pod * wstaw aktualny numerek 😉). Ewentualnie używajmy ES6+, gdzie + oznacza wszystkie następne wydane standardy.

Dobra...tyle tytułem wstępu...zobaczmy co nowego wprowadza do języka standard ES9.

This specification, the 9th edition, introduces support for asynchronous iteration via the AsyncIterator protocol and async generators. This specification also includes four new regular expression features: the dotAll flag, named capture groups, Unicode property escapes, and look-behind assertions. It also includes rest parameter and spread operator support for object properties. There have also been many minor updates, editorial and normative, with many contributions from our awesome community.

AsyncIterator

W ES6 dostaliśmy interfejs iteratora. Pozwala on na dostosowanie iteracji po obiekcie wg. swoich potrzeb.

Np. do generowania ciągu Fibonnaciego możemy użyć poniższego kodu:

const fibonacci = {
    [Symbol.iterator]() {
        let pre = 0, cur = 1
        return {
           next () {
               [ pre, cur ] = [ cur, pre + cur ]
               return { done: false, value: cur }
           }
        }
    }
}

for (let n of fibonacci) {
    if (n > 10)
        break
    console.log(n)
}

Wynikiem tego skryptu będzie wypisanie na konsoli liczb: 1, 2, 3, 5 i 8.

Do tej pory iterować mogliśmy tylko po synchronicznych strukturach danych. Związane to było z interfejsem next, który wg. specyfikacji ES6 zwara obiekt { done, value }. Gdzie done informuje nas, że dotarliśmy do końca naszego iterowanego obiektu, a value kryje - zaskakujące - interowaną wartość ;)!

Standard ES9 wprowadza pojęcie AsyncIteratora, dzięki któremu next zwraca teraz promise.

Aby iterować po asynchronicznych strukturach danych używamy for-await-of

async function example() {
  const arrayOfFetchPromises = [
    fetch('1.txt'),
    fetch('2.txt'),
    fetch('3.txt')
  ];

  for await (const item of arrayOfFetchPromises) {
    console.log(item); // Logs a response
  }
}

Po więcej info odsyłam do https://github.com/tc39/proposal-async-iteration

RegExp features

Wyrażenia regularne...ehh...wyrażenia regularne. W swojej karierze spotkałem tylko jednego człowieka, który RegExp'y klepał z głowy :) Cała reszta, włącznie ze mną, korzysta z narzędzi typu https://regex101.com/. W ES9 dostaliśmy klika nowych funkcjonalności dotyczących właśnie wyrażeń regularnych.

dotAll flag

. - kropka - w wyrażeniach regularnych oznacza single character - pojedynczy znak. Jest jednak pewien szkopuł - pod pojęciem single character nie znajduje się żaden znak nowej linii.

Dlatego taki kod nie zadziała:

/foo.bar/.test('foo\nbar'); // false

Do tej pory musieliśmy użwać "obejść" w stylu /foo[\s\S]bar bądź /foo[^]bar.

  • \s, \S - szukają odpowiednio białych znaków i "nie białych znaków" ;) \S to negacja
  • ^ - znak negacji...czyli szukamy wszystkiego czym nie jest...puste wyrażenie - więc szukamy słów, liczb i białych znaków ;)

Z pomocą przychodzi flaga dotAll którą ustawiamy poprzez dodanie literki s. Od teraz możemy użwać . jako dosłownie "każdy znak":

/foo.bar/s.test('foo\nbar'); // true

Named capture groups

W wyrażeniach regularnych mamy możliwość tworzenia grup. Do tej pory, aby odczytać wartość grupy należało dobrać się do odpowiedniego miejsca w tablicy zwracanej przez metodę exec.

Załóżmy, że w ciągu znaków chcemy przechowywać dane na temat przedziału czasu w formacie P10D. Gdzie P oznacza period (okres), następnie mamy liczbę, a na końcu jednostkę M - miesiące, D - dni, Y - lata.

Przykład po staremu:

const r = /P(\d+)([DMY])/;

const result = r.exec("P10M");
const value = result[1];
const unit = result[2];

console.log({ value, unit }); // { value: "10", unit: "M" }

ES9 wprowadza możliwość nadania nazwy dla grup.

Robimy to poprzez konstrukcję ?<nazwa_grupy>.

const r = /P(?<value>\d+)(?<unit>[DMY])/;

const result = r.exec("P10M");

const { value, unit } = result.groups; // destructuring pattern from ES6
console.log({ value, unit }); // { value: "10", unit: "M" }

Nigdy więcej magic numbers przy pobieraniu wartości grup! 😎

Unicode property escapes

Zastanawialiście się kiedyś jak napisać regexpa który znajdzie nam użyte emoji? No właśnie - ja też nie ;) ale kto wie, może kiedyś się przyda :D ... Jak to zrobić?

Standard Unicode przypisuje do znaków różne atrybuty wraz z ich wartościami. Np. znak π (pi) ma przypisany atrybut Script z wartością Greek. Emoji mają przypisany atrybut Emoji itd.

ES9 wprowadza nowy escape character = \p, po którym wpisujemy atrybut i wartość z Unicode jaka nas interesuje.

Aby znaleźć znaki emoji użyjemy następującego wyrażenia regularnego:

const r = /\p{Emoji}/u;

r.test("a") // false
r.test("🎉") // true

To jest tylko jeden ze sposobów wykorzystania Unicode property escapes. Przychodzą Wam jeszcze jakieś do głowy? Dajcie znać w komentarzach ;)

Look-behind assertions

Przed ES9 do dyspozycji mieliśmy asercje typu look-ahead. Polegały one na tym, że szukaliśmy wyrażenia, po którym następuje pewna sekwencja; przy czym sekwencja ta nie jest brana pod uwagę przy zwróconym porównaniu...heh...jak sam to czytam to nie do końca chyba rozumiem - spróbujmy na przykładzie :)

Załóżmy że mamy adress email: user@example.com i chcielibyśmy znaleźć i zamienić nazwę domeny bez rozszerzenia.

Możemy posłużyć się następującym wyrażeniem regularnym:

const r = /@.+(?=\.[a-z]{2,4})/

"user@example.com".replace(r, "@domain") // user@domain.com

Konstrukcja typu (?=...) sprawia że szukamy tego co kryje się pod ... ale znaleziona wartość nie będzie brana pod uwagę.

W ES9 doszła nam asercja typu look-behind. Załóżmy, że nie chcemy aby znak @ nie był brany pod uwagę przy zamianie.

Wystarczy, że użyjemy konsrtukcji (?<=...)

const r = /(?<=@).+(?=\.[a-z]{2,4})/;
"user@example.com".replace(r, "domain") // user@domain.com

Jest też możliwość negacji asercji za pomocą składni (?<!...).

Przykład? Załóżmy, że na stronie mamy ceny w euro (€) i dolarach ($). Chcemy znaleźć tylko te, poprzedzone znakiem $.

Używamy negacji asercji look-behind:

const text = "$1 €2 $6";
const r = /(?<!€)\d/g;

console.log(text.match(r)) // [1,6]

Promise.prototype.finally

Moża by rzec - WRESZCIE! :) To jest to czego mi w Promisach brakowało. Co trzeba zrobić aby wykonać kod "zawsze" - nie ważne od tego czy Promise jest resolved czy rejected ?

Po staremu:

fetch("/something.txt")
    .catch(() => {}) // first catch then 'then'
    .then(() => console.log("finally"))

Teraz możemy użyć po prostu słówka finally:

fetch("/something.txt")
    .finally(() => console.log("finally"))

Rest, spread in Object

Operatory rest i spread zostały wprowadzone już w ES6. Niestety używać ich mogliśmy tylko w kontekście tablic i argumentów funkcji.

Bardzo ciekawą opcją jest użycie rest'a w kontekście obiektu:

const a = { a: "a" }; // { a: "a" }
const b = { b: "b", ...a }; // { a: "a", b: "b" }

Niestety - żeby pisać w ten sposób kod potrzebowaliśmy pluginu-babela transform-object-rest-spread.

Od wersji ES9 możemy ten plugin wyrzucić do kosza ;) ponieważ standard wspiera już taki zapis natywnie.


To chyba tyle :) Jeśli jesteście zainteresowani szczegółami zapraszam na oficjalną stronę standardu ECMAScript ;)

Natomiast tu możecie znaleźć propozycje, które zostały już zaakaceptowane i wejdą w następnych wersjach standardu.

Miłego weekendu!