ES09 - Novas features no JavaScript
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
- Rest e Spread Operators
- Promise: finally()
- Template Literal Revision
- Regular Expression features
- Conclusão
- Links
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})
Pode ficar melhor ainda:
(?<dia>\\d{2})-(?<mes>\\d{2})-(?<ano>\\d{4})
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.
🔗 Links
Propostas do Comitê Técnico 39 - ECMAScript | TC39:
- https://github.com/tc39/proposal-async-iteration
- https://github.com/tc39/proposal-object-rest-spread
- https://github.com/tc39/proposal-promise-finally
- https://github.com/tc39/proposal-template-literal-revision
- https://github.com/tc39/proposal-regexp-named-groups
- https://github.com/tc39/proposal-regexp-lookbehind
- https://github.com/tc39/proposal-regexp-dotall-flag
Algumas fontes:
- https://exploringjs.com/es2018-es2019/toc.html
- https://stackoverflow.com/questions/2973436/regex-lookahead-lookbehind-and-atomic-groups
- https://javascript.info/async-iterators-generators
- https://regex101.com/
- https://www.codementor.io/@tiagolopesferreira/asynchronous-iterators-in-javascript-jl1yg8la1
- https://developer.mozilla.org/pt-BR/
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! 🚀