ES09 - Novas features no JavaScript

JavaScript 31 de Ago de 2020

Neste post vamos ficar por dentro das novidades do ES09 (ECMAScript 2018) no JavaScript.

Se quiser ver o que rolou no ES08, recomendo a leitura desse post.

Tópicos

Asynchronous Iteration

Iterar é um processo de repetição, por exemplo: em um array de três elementos: ['a', 'b', 'c'] você pode iterar (percorrer) para acessar cada valor. Dizemos iterável é uma estrutura de dados que pode ser acessado via iteração, no JavaScript, Array, String, Map e Set são iteráveis.

Foi introduzida na linguagem uma interface para Interação Assíncrona:

interface AsyncIterable<T> {
  [Symbol.asyncIterator]() : AsyncIterator<T>;
}
interface AsyncIterator<T> {
  next() : Promise<IteratorResult<T>>; // Retorna uma Promise
}
interface IteratorResult<T> {
  value: T;
  done: boolean;
}

A diferença da interface de iteração síncrona é que ela devolve o próprio resultado do elemento.

interface Iterable<T> {
  [Symbol.iterator]() : Iterator<T>;
}
interface Iterator<T> {
  next() : IteratorResult<T>; // Retorna o valor direto
}
interface IteratorResult<T> {
  value: T;
  done: boolean;
}

Na prática podemos iterar usando for-await-of nos async iterators. Veja o exemplo abaixo:

(async function exemplo() {
  
	const arrayGithubUsers = [
    fetch("https://api.github.com/users/diego3g"),
    fetch("https://api.github.com/users/tgmarinho"),
    fetch("https://api.github.com/users/vinifraga")
  ];

  // Sync Iterator
  for (const item of arrayGithubUsers) {
    console.log(item); // Logs a promise
  }

  // Async Iterator
  for await (const item of arrayGithubUsers) {
    console.log(item); // item disponível
    const user = await item.json(); // mostrando o item em formato JSON.
    console.log(user.login); // Logs o username: diego3g, tgmarinho, vinifraga
  }

})();

Link para o código, caso queira testar.

Como estamos lidando com código assíncrono, criei uma Immediately Invoked Async Function Expression. Coloquei três promises (prometo buscar os usuários do github —  then, mas não garanto — catch) no array arrayGithubUsers  e usei o for-await-of  para iterar. Em cada item a Promise será resolvida e o valor repassado.

Rest e Spread Operators

Os operadores Rest e Spread são formas de recuperarmos o conteúdo de objetos, vetores e parâmetros de funções de forma rápida.

  • Destructuring

Para falar de ambos, precisamos antes revisar a desestruturação (destructuring assignment). Essa técnica permite extrair os valores de objetos e arrays em outra variável.

Exemplo com Objetos:

const document = {
  id: 1,
  title: 'titulo',
  date: '01/08/2020',
  owner: 'Thiago'
}

// Se quiser pegar cada atributo:

const id = document.id;
const title = document.title;
const date = document.date;
const owner = document.owner;

// Aff que canseira

console.log(id, title, date, owner)

// Com desestruturação podemos fazer a mesma coisa, 
// porém com poucas linhas de código

const { id, title, date, owner } = document

console.log(id, title, date, owner)

// Muito melhor!

Perceba este detalhe, o nome da variável deve ser o mesmo fornecido na propriedade do objeto.

const { x, title, date, owner } = document

console.log(x) // undefined

Exemplo com Array:

const numbers = [1, 2 ];

const [ a, b ] = numbers;

console.log(a); // 1
console.log(b); // 2

// Compare com a maneira antiga recuperar valores de um array

const a = numbers[0]
const b = numbers[1]

// Veja novamente como ficou bem melhor com desesestruturação

const [ a, b ] = numbers;
  • Rest

Em uma tradução literal, significa 'resto'. Operador Rest cria um novo array com o que sobrou, ou seja, o resto.

const numbers = [1, 2, 3, 4, 5];

const [a, b, ...rest] = numbers;

console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]

Acima podemos ver a desestruturação acontecendo, foram criadas três variáveis: a, b e rest, cada um com seu respectivo valor, rest continua sendo um array com o que sobrou [3, 4, 5].

Obrigatoriamente, o operador ...  tem que ser o último elemento do Array.

const numbers = [1, 2, 3, 4, 5];

const [a, b, ...rest, ...raspa_do_tacho] = numbers; // ❌

// SyntaxError: /src/index.js: Rest element must be last element (8:20)
  • Spread

Tradução literal de Spread é espalhar ou propagar. Utiliza a mesma sintaxe ... do Rest Operator mas a funcionalidade é diferente.

Spread serve para desestruturar um array ou um objeto em outro array ou objetivo respectivamente.

const document = {
  id: 1,
  title: "titulo",
  date: "01/08/2020",
  owner: "Thiago"
};

const newDocument = { ...document };

console.log(newDocument);

Usando a sintaxe de espalhamento (Spread Syntax) criei um outro documento com as mesmas propriedades e valores.

Com Spread podemos fazer muito coisa. Veja a flexibilidade:

const document = {
  id: 1,
  title: "titulo",
  date: "01/08/2020",
  owner: "Thiago"
};

const documentoComAnexo = { 
     ...document, attached: "file.doc" 
};

console.log(documentoComAnexo);

/**
	id: 1
	title: "titulo"
	date: "01/08/2020"
	owner: "Thiago"
	attached: "file.doc" // Nova Propriedade
/*

Veja que foi criado um novo objeto documentoComAnexo com uma cópia de document, e uma nova propriedade no objeto attached (anexado) e seu valor.

Para um exemplo mais avançado, observe que está sendo retornado um objeto com todos os dados de appointment, e está sendo criada uma nova propriedade: hourFormatted (hora formatada).

return {
        ...appointment,
        hourFormatted: format(parseISO(appointment.date), 'HH:mm'),
};

Isso ajuda demais o(a) Dev no cotidiano.

Mais um exemplo amplamente utilizado em aplicações React:

Atualizando o array de mensagens com um novo valor:

const [messages, setMessages]  = useState([])
// ...
const message = { id :1, title: 'Fala Dev!!!', subject: 'Já acessou a nova plataforma da Rocketseat?' };
setMessages([...messages, message]);

console.log(messages) 
// [ {}, {}, { id :1, title: 'Fala Dev!!!', subject: 'Já acessou a nova plataforma da Rocketseat?' } ]

Na Programação funcional com JavaScript utiliza-se bastante esse tipo de código.

Promise: finally()

Finalmente, finally() foi incluída dentro das Promises. 👌

Agora as promises vão se parecer com:

iPromise
.then(result => {···}) // Executa apenas se a Promise foi resolvida.
.catch(error => {···}) // Executa apenas se a Promise foi rejeitada.
.finally(() => {···}); // Executa após o then() ou catch(). Sempre será executada.

Ao contrário de then() e catch(), finally sempre será executada, independente do sucesso ou falha para obter o resultado.

Sempre que terminar de utilizar um recurso deve-se encerrar o mesmo — isso será feito dentro do bloco de códigos do método finally().

let connection;
db.open()
.then(conn => {
    connection = conn;
    return connection.select({ name: 'Robson' });
})
.then(result => {
    // Promise resolvida 😉
    // utilize `connection` para realizar outras queries por exemplo.
})
···
.catch(error => {
    // Ops! Lide com esse erro  😇
})
.finally(() => {
    connection.close(); // Finalizou a Promise, encerre a conexão. 🙋‍♂️
});

Exemplo prático:

const user = "tgmarinho";

fetch(`https://api.github.com/users/${user}`)
  .then((user) => {
    return user.json();
  })
  .then(({ login: loginDoGithub }) => {
    if (!loginDoGithub) throw new Error("Ops! Usuário não encontrado");
    console.log(loginDoGithub);
  })
  .catch((error) => console.error(error))
  .finally(() => {
    console.log("Como foi sua experiência? ");
    console.log("A Promise foi Resolvida ou Rejeitada? ");
  });

Se o usuário for encontrado, será impresso o login de identificação e logo em seguida outro console.log será impresso, perguntando da sua experiência. Se o usuário não for encontrado, será lançado um erro e o catch vai pegá-lo e imprimi-lo no console.

Essa funcionalidade é semelhante ao try/catch/finally síncrono.

Template Literal Revision

Com as Templates Strings do ES06 podemos fazer concatenação de Strings dessa maneira:

const language = "JavaScript";
const platform = "Web, Desktop e Mobile";
console.log(`Com ${language} podemos desenvolver projetos para ${platform}`); 
// Resultado: Com JavaScript podemos desenvolver projetos para Web, Desktop e Mobile

O que deixou o código muito mais limpo e agradável. Legal que Template String permite criar multilinhas de maneira mais clean.

Além de concatenação, podemos incluir expressões matemáticas também, exemplo:

let a = 7;
let b = 1;
console.log(`Oito é 
${a + b} e
não 
${2 * a + b}.`);
// Oito é 
// 8 e
// não 
// 15.

Depois surgiu às Tagged Template Strings — uma versão mais avançada do Template String.

const a = 2;
const b = 4;

function tag(strings, ...values) {
  console.log(strings[0]); // "Hello "
  console.log(strings[1]); // " Rocketseat"
  console.log(values[0]); // 6
  console.log(values[1]); // 8

  return "Boost Yourself.";
}

tag`Hello ${a + b} Rocketseat ${a * b}`; 
// podemos invocar a função dessa maneira

console.log(tag`Hello ${a + b} Rocketseat ${a * b}`);
// "Boost Yourself.!"

Observe que foi criado uma função chamada tag e a invocação dela é feita com crases ``, no primeiro parâmetro é passado as Strings e o segundo o código (expressão).

Essas funcionalidades são utilizadas no GraphQL e Styled Components:

import styled, { css } from 'styled-components';

const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

export cssHelper = css`
  border: 1px solid ${props => props.theme.borderRadius};
`;

Foi feita uma revisão no Template Literal, daí do nome Template Literal Revision, que removeu restrições relativas a sequências de escapes. As sequências inválidas terão um valor undefined e serão mostrados no método raw.

console.log(tagFunc`\\u{4B}`); // válido - Unicode que representa a letra K
/**
 * Object {Cooked: Array[1], Raw: Array[1]}
  Cooked: Array[1]
  0: "K"
  Raw: Array[1]
  0: "\\u{4B}"
 */

console.log(tagFunc`\\u{4xxxB}`); // inválido - Unicode que representa nada.
/**
 * Object {Cooked: Array[1], Raw: Array[1]}
  Cooked: Array[1]
  0: undefined
  Raw: Array[1]
  0: "\\u{4xxxB}"
 */

Regular Expression features

  • RegExp named capture groups

Grupos de captura nomeados no RegExp. Essa atualização deixa as expressões regulares mais fáceis de ler e entender.

Então a proposta foi dar nomes aos bois, digo, nome aos grupos de captura:

Regex que encontra datas:

(\\d{2})-(\\d{2})-(\\d{4})
Online regex tester and debugger: PHP, PCRE, Python, Golang and JavaScript
Online regex tester, debugger with highlighting for PHP, PCRE, Python, Golang and JavaScript.

Pode ficar melhor ainda:

(?<dia>\\d{2})-(?<mes>\\d{2})-(?<ano>\\d{4})
Online regex tester and debugger: PHP, PCRE, Python, Golang and JavaScript
Online regex tester, debugger with highlighting for PHP, PCRE, Python, Golang and JavaScript.

Agora ficou mais claro que estamos pegar na regex uma data com dia, mês e ano.

Os grupos nomeados podem ser acessados a partir de propriedades do resultado da expressão regular:

let regex = /(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})/u;
let result = regex.exec('2020-08-28');
// result.groups.year === '2020';
// result.groups.month === '08';
// result.groups.day === '28';

// result[0] === '2020-08-28';
// result[1] === '2020';
// result[2] === '08';
// result[3] === '28';

Com desestruturação fica ainda muito melhor:

let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
console.log(`one: ${one}, two: ${two}`);  // prints one: foo, two: bar

Basicamente foi implementado uma documentação na Regex! 😃

  • RegExp Unicode property escapes

Escapando propriedades unicode no RegExp. Foi introduzida uma API nativa na linguagem para lidar com unicode no Regex. Antes era necessário libs para fazer isso, agora o JavaScript dá suporte:

console.log(/^\\p{White_Space}+$/u.test("\\t \\n\\r")); // true - espaço em branco encontrado
console.log(/^\\p{Script=Greek}+$/u.test("μετά")); // true - letras em grego encontradas
  • RegExp Lookbehind assertions

Semelhante à técnica de Look ahead porém na direção oposta, enquanto o Look ahead verifica o sucessor (o que vem depois) o Look behind analisa o precessor (o que vem antes):

Exemplos:

Look behind positive (?<=)

Encontre a expressão A  onde a expressão B  precede:

(?<=B)A

Look behind negative (?<!)

Encontre a expressão A onde a expressão B não precede:

(?<!B)A

Veja um exemplo:

const REGEX_ROCKET_PREFIX = /(?<=\\🚀)rocket/g;
console.log(
  "🚀rocket 👨‍💻rocket 👩‍💻rocket rocket".replace(REGEX_ROCKET_PREFIX, "💜")
);
// 🚀💜 👨‍💻rocket 👩‍💻rocket rocket

No exemplo acima, toda a palavra "rocket" que antes tem um emoji de 🚀,substitua a palavra "rocket" pelo emoji de 💜 .

Mesma Regex mas sem o lookbehind, fica um pouco deselegante:

const RE_ROCKET_PREFIX_NEGATIVE = /(?<!\\🚀)rocket/g;
console.log(
  "🚀rocket 👨‍💻rocket 👩‍💻rocket rocket".replace(RE_ROCKET_PREFIX_NEGATIVE, "💜")
);
// 🚀rocket 👨‍💻💜 👩‍💻💜 💜

Só corresponde se a palavra não preceder do emoji  🚀.

Ainda está ai dev? 🙂 Bora para a última atualização:

  • (dotAll) flag for regular expressions

O ponto . na Regex é um coringa 🃏 que encontra (corresponde/matches) qualquer carácter. Exceto quebras de linhas \n.

A proposta inclui uma nova flag chamada dotAll, representada por um /s (abreviatura de "singleline"). Que permite encontrar uma quebra de linha \\n e "transformar" em um ponto . .

const str1 = "Boost\\nYourself! um próximo nível!";
console.log(str1);
// Boost
// Yourself! um próximo nível!

const regex1 = new RegExp("Boost.Yourself!", "s");

console.log(regex1.dotAll); // true

console.log(str1.replace(regex1, "Sempre haverá"));
// Sempre haverá um próximo nível! 

const str2 = "Boost\\nYourself! um próximo nível!";
console.log(str2);
// Boost
// Yourself! um próximo nível! 

const regex2 = new RegExp("Boost.Yourself!");

console.log(regex2.dotAll); // false

console.log(str2.replace(regex2, "Sempre haverá"));
// Boost
// Yourself! um próximo nível!

Observe que no primeiro exemplo, usando a flag "s" (dotAll), a quebra de linha \\n  acaba sendo correspondida com um .

Clareando ainda mais:

const str3 = "Bo\\nost\\nYour\\nse\\nlf";
console.log(str3);
/*
Bo
ost
Your
se
lf
*/

const pattern = "Bo.ost.Your.se.lf";

const regex3 = new RegExp(pattern, "s");
console.log(regex3.dotAll); // true

const stringRe = str3.replace(regex3, pattern);

console.log("Nova String:", stringRe);

Criei uma nova string chamada stringRe que recebe a substituição do padrão definido na regex3 pela String pattern. Observe que todas as quebras de linhas foram reconhecidos como ., e daí do nome .dotAll

DotAll para todos \\n. 👌

Conclusão

Teve boas features lançadas nessa versão. As mais utilizadas são Spread e Rest. A possibilidade de fazer iteração assíncrona é muito boa usando for await of. As melhorias nas expressões regulares tornando-as mais semânticas e inteligíveis para o(a) dev foi um excelente ponto.

Propostas do Comitê Técnico 39 - ECMAScript | TC39:

Algumas fontes:

E aí, o que achou do post? Qual feature você mais gostou e utiliza no dia a dia?

Espero que tenha curtido! 💜

O aprendizado é contínuo e sempre haverá um próximo nível! 🚀

Marcadores

Thiago Marinho

Dev Apaixonado por Tecnologia & Educação! Evolua rápido como a tecnologia, aprendizado é contínuo e sempre haverá um próximo nível. Boost Yourself! 🚀