A especificação ECMAScript é como um retrato da linguagem JavaScript que é repintada a cada ano. Como é típico do JavaScript moderno, as especificações e a prática do mundo real movem-se em conjunto. A versão mais recente da especificação, ECMAScript 2024, inclui sete novos recursos JavaScript e deverá ser finalizada em junho. Este artigo apresenta quatro dos novos recursos que já estão disponíveis em navegadores e ambientes de servidor e prontos para uso hoje:
- Promessa.comResolvers é um mecanismo poderoso para gerenciar operações assíncronas quando é necessário controle externo sobre resolução e rejeição.
- Object.groupBy e Map.groupBy permitem organizar coleções com base em propriedades principais.
- Atômicos.waitAsync facilita a comunicação segura e a sincronização entre threads de trabalho.
- String.isWellFormed e String.toWellFormed adicione ferramentas valiosas para lidar com entradas do usuário e dados de rede.
Promessa.comResolvers
Vamos começar com o novo método estático em Promise
chamado withResolvers()
. As promessas do JavaScript nos dão várias maneiras de lidar com operações assíncronas. O withResolvers()
método é usado para criar as três partes de um Promise
: o Promise
ele mesmo e o resolve()
e reject()
funções.
O benefício de withResolvers()
é que ele cria todos os três como referências expostas externamente. Nos casos em que se deseja criar uma promessa, e também ter acesso à resolução e rejeição da promessa a partir de código externo, este é o método a utilizar.
A especificação em si é caracteristicamente espartana em sua descrição. A documentação do Mozilla fornece mais detalhes. A principal lição é que withResolvers()
é equivalente a:
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// use resolve and reject to control the promise
No trecho acima, declaramos o resolve
e reject
referências no escopo anexo e, em seguida, use-as dentro do corpo do Promise
retorno de chamada para consultar o resolve
e reject
argumentos. Dessa forma, estamos controlando o retorno de chamada da promessa fora do próprio retorno de chamada.
O Promise.withResolvers()
a sintaxe é mais compacta e não precisamos declarar o resolve
e reject
separadamente. Com este método, poderíamos escrever exatamente a mesma funcionalidade acima, assim:
let { promise, resolve, reject } = Promise.withResolvers();
A essência desse recurso é que você o utiliza quando precisa de acesso externo a resolve()
e reject()
. Este não é um cenário muito comum, mas acontece. Vamos considerar um exemplo simples:
function race(operation1, operation2) {
const { racePromise, resolve, reject } = Promise.withResolvers();
operation1().then(resolve).catch(reject);
operation2().then(resolve).catch(reject);
return racePromise;
}
function fetchData1() {
return new Promise((resolve) => {
setTimeout(() => resolve("Data from source 1"), 1000);
});
}
function fetchData2() {
return new Promise((resolve) => {
setTimeout(() => resolve("Data from source 2"), 500);
});
}
race(fetchData1, fetchData2)
.then((data) => console.log("Winner:", data))
.catch((error) => console.error("Error:", error));
Aqui, temos duas operações, fetchData1()
e fetchData2()
, esse retorno promete. Eles executam tempos limite e fetchData2()
é sempre mais rápido em 500 milissegundos. Nós usamos withResolvers()
dentro de race()
função para expor o resolve
e reject
funções. Essas funções são então usadas por uma nova promessa, chamada racePromise
.
Usamos então o resolve
e reject
funções para responder aos dois fetchData
operações. Isso é interessante porque você pode ver que nem sequer fornecemos um retorno de chamada para racePromise
. Em vez disso, controlamos a promessa externamente. Usamos esse controle para vincular o resultado das outras duas promessas ao racePromise
.
Este é um exemplo inventado e um tanto irrealista porque as duas promessas das corridas não começam ao mesmo tempo. O objetivo é mostrar a essência de como e quando usar withResolvers()
.
Object.groupBy e Map.groupBy
O prático método groupBy é uma maneira rápida de organizar coleções com base em um valor de string. É um método estático em Object
e Map
, que funciona em uma coleção semelhante a um array. O groupBy()
O método recebe dois argumentos: a coleção e um retorno de chamada que opera nela. Você recebe de volta uma nova instância de objeto que possui valores de rótulo de string como chaves e os elementos de matriz correspondentes como valor.
Portanto, quando você tem um array e precisa dividir os elementos em grupos rotulados por strings de acordo com alguns critérios internos, este é o método a ser usado.
Isso é algo bastante comum que surge na codificação do dia-a-dia. Olhar para um exemplo deve deixar isso claro. Digamos que temos uma coleção de raças de cães e seus tamanhos:
const dogBreeds = (
{ breed: 'Shih Tzu', size: 'Toy' },
{ breed: 'Leonberger', size: 'Giant' },
{ breed: 'King Charles Spaniel', size: 'Toy' },
{ breed: 'Great Pyrenees', size: 'Giant' },
{ breed: 'Corgi', size: 'Small' },
{ breed: 'German Shepherd', size: 'Large' },
);
Agora, digamos que queremos organizar esta coleção por tamanho. Queremos terminar com uma coleção de objetos onde as chaves são o tamanho da raça e os valores são a raça do cachorro. Normalmente, escreveríamos um loop para fazer isso, mas é um pouco complicado; parece que deveria haver uma maneira melhor. Agora com groupBy()
há:
groupBy() is that better way:
Object.groupBy(dogBreeds, (x) => {
return x.size;
})
This gives us output like so:
{ "Toy": (
{ "breed": "Shih Tzu", "size": "Toy" },
{ "breed": "King Charles Spaniel", "size": "Toy" }
),
"Giant": (
{ "breed": "Leonberger", "size": "Giant" },
{ "breed": "Great Pyrenees", "size": "Giant" }
),
"Small": (
{ "breed": "Corgi", "size": "Small" }
),
"Large": (
{ "breed": "German Shepherd", "size": "Large" }
)
}
Isso oferece uma maneira simples e funcional de agrupar coleções de objetos com base em alguma propriedade dos próprios objetos.
O groupBy()
O método pega tudo o que é retornado pelo retorno de chamada e coleta automaticamente todos os elementos que são iguais de acordo com String equality
. Se o valor de retorno não for uma string, ele será convertido em uma string. Se não puder ser coagido, ocorrerá um erro.
Atômicos.waitAsync
O novo Atomics.waitAsync()
O método foi projetado para compartilhar dados entre threads de trabalho com segurança. Não bloqueia o thread principal como Atomics.wait()
faz. Por ser usado entre threads, ele depende do SharedArrayBuffer
aula.
Esta classe está desabilitada por padrão em navegadores modernos, a menos que os requisitos de segurança sejam atendidos. No Node.js, entretanto, a classe está habilitada por padrão.
Aqui está um exemplo simples de uso no Node. Observe que as importações são integradas ao Node, portanto, nenhuma instalação do NPM é necessária (mas observe que o import
afirmação é):
// asyncWait.js
const { Worker, isMainThread, parentPort } = require('worker_threads');
const sharedBuffer = new SharedArrayBuffer(4);
const int32Array = new Int32Array(sharedBuffer);
if (isMainThread) {
const worker = new Worker(__filename);
async function waitForMessage() {
const initialValue = 0;
const result = await Atomics.waitAsync(int32Array, 0, initialValue);
if (result.async) {
console.log("Message received from worker:", int32Array(0));
} else {
console.error("Timeout waiting for worker message");
}
}
waitForMessage();
} else {
setTimeout(() => {
Atomics.store(int32Array, 0, 1);
}, 2000);
}
Para executar este programa, basta digitar: $ node asyncWait.js
O programa declara um SharedArrayBuffer
(enrolado em um int32Array
) e então verifica se estamos no thread principal. Se for o thread principal, geramos um thread de trabalho. (Se você é novo em threads de trabalho, aqui está uma boa introdução.)
O thread principal espera por uma atualização do trabalhador através do Atomics.waitAsync()
chamar. Os três argumentos para waitAsync(1, 2, 3)
são:
- Matriz compartilhada (
int32Array
): O espaço de memória compartilhada. - Elemento a ser observado (
0
): O índice da matriz a ser aguardada. - Valor inicial (
initialValue = 0
): notificar o thread principal apenas se esse valor for diferente (ou seja, se o trabalhador definir o valor para o valor inicial de 0, uma notificação não ocorrerá).
String.isWellFormed e String.toWellFormed
Entrada do usuário, dados incorretos e falhas de rede são fontes de strings malformadas. String.isWellFormed
é uma verificação de sanidade. Ele determina se uma string é válida em UTF-16. UTF-16 é a codificação que o próprio JavaScript usa, então String.isWellFormed()
garante que uma determinada string seja algo que o JavaScript possa manipular:
const string1 = "Hello, InfoWorld!";
const string2 = "Hello, uD800world!";
console.log(string1.isWellFormed()); // Output: true (well-formed)
console.log(string2.isWellFormed()); // Output: false (malformed)
Você pode aprender mais sobre o que constitui strings válidas em JavaScript nesta seção da referência do Mozilla. Os bandidos na codificação UTF-16 são conhecidos como “substitutos solitários”.
O parceiro ativo para String.isWellFormed
, String.toWellFormed
transforma uma determinada string em algo válido. Quaisquer substitutos isolados encontrados serão substituídos por U+FFFD, o caractere de ponto de interrogação de losango preto: �.
"Hello, uD800world!".toWellFormed()
// outputs: 'Hello, �world!'
Conclusão
Temos uma bela coleção de novos recursos no ECMAScript 2024. Promise.withResolvers()
e Atomics.waitAsync()
são casos de uso mais avançados, enquanto groupBy
é uma adição conveniente que muitas vezes é útil, e os novos métodos de string são perfeitos para determinadas situações. Todos esses recursos são compatíveis com JavaScript em navegadores e ambientes de servidor, portanto você pode começar a usá-los hoje mesmo.