É o começo de um novo ano e, embora muitas pessoas prometam ser mais ativas, vou mostrar a você como fazer Promise
s para ser mais preguiçoso…Promise
, isto é.
Fará mais sentido daqui a pouco.
Primeiro, vamos ver um exemplo básico de Promise
. Aqui, tenho uma função chamada sleep que leva um tempo em milissegundos e um valor. Ele retorna uma promessa que executará um setTimeout
pelo número de milissegundos que devemos esperar; então a promessa resolve com o valor.
/** * @template ValueType * @param {number} ms * @param {ValueType} value * @returns {Promise<ValueType>} */ function sleep(ms, value) { return new Promise((resolve) => { setTimeout(() => resolve(value), ms); }); }
Funciona assim:
Podemos aguardar a função sleep
com os argumentos 1000
e 'Yawn & stretch'
e, após um segundo, o console
registrará a string 'Yawn & stretch'.
Não há nada de muito especial nisso. Ele provavelmente se comporta como você esperaria, mas fica um pouco estranho se o armazenarmos como uma variável para await
mais tarde, em vez de await
o Promise
retornado imediatamente.
const nap = sleep(1000, 'Yawn & stretch')
Agora, digamos que fazemos algum outro trabalho que leva tempo (como digitar o próximo exemplo) e, em seguida, await
a variável nap
.
Você pode esperar um atraso de um segundo antes de resolver, mas, na verdade, ele resolve imediatamente. Sempre que você cria um Promise
, você instancia qualquer funcionalidade assíncrona pela qual ele é responsável.
Em nosso exemplo, no momento em que definimos a variável nap
, é criada a Promise
que executa o setTimeout
. Como sou um digitador lento, a Promise
será resolvida no momento em que a await
.
Em outras palavras, Promise
s estão ansiosos. Eles não await
que você os espere.
Em alguns casos, isso é bom. Em outros casos, pode levar ao uso desnecessário de recursos. Para esses cenários, você pode querer algo que se pareça com um Promise
, mas use
Antes de continuarmos, quero mostrar-lhe algo interessante.
As Promise
não são as únicas coisas que podem ser await
em JavaScript. Se criarmos um Object
simples com um método .then()
, podemos await
esse objeto como qualquer Promise
.
Isso é meio estranho, mas também nos permite criar diferentes objetos que se parecem com Promise
s, mas não são. Esses objetos às vezes são chamados de “
Com isso em mente, vamos criar um novoLazyPromise
que estende o construtor interno de Promise
. Estender Promise não é estritamente necessário, mas faz com que pareça mais semelhante a uma Promise
usando coisas como instanceof
.
class LazyPromise extends Promise { /** @param {ConstructorParameters<PromiseConstructor>[0]} executor */ constructor(executor) { super(executor); if (typeof executor !== 'function') { throw new TypeError(`LazyPromise executor is not a function`); } this._executor = executor; } then() { this.promise = this.promise || new Promise(this._executor); return this.promise.then.apply(this.promise, arguments); } }
A parte a ser focada é o método then()
. Ele sequestra o comportamento padrão de um Promise
padrão para esperar até que o método .then()
seja executado antes de criar um Promise
real.
Isso evita instanciar a funcionalidade assíncrona até que você realmente a chame. E funciona se você chamar explicitamente .then()
ou usar await
.
Agora, vamos ver o que acontece se substituirmos o Promise
na função sleep
original por um LazyPromise
. Mais uma vez, atribuiremos o resultado a uma variável nap
.
function sleep(ms, value) { return new LazyPromise((resolve) => { setTimeout(() => resolve(value), ms); }); } const nap = sleep(1000, 'Yawn & stretch')
Em seguida, tomamos nosso tempo para digitar a linha await nap
e executá-la.
Desta vez, vemos um atraso de um segundo antes que a Promise
seja resolvida, independentemente de quanto tempo passou desde que a variável foi criada.
(Observe que esta implementação apenas cria o novo Promise
uma vez e faz referência a ele em chamadas subsequentes. Portanto, se await
novamente, ele será resolvido imediatamente como qualquer Promise
normal )
Claro, este é um exemplo trivial que você provavelmente não encontrará no código de produção, mas há muitos projetos que usam objetos do tipo Promise
avaliados preguiçosamente. Provavelmente, o exemplo mais comum é com bancos de dados ORM s e construtores de consulta como
Considere o pseudo-código abaixo. É inspirado por alguns destes construtores de consulta:
const query = db('user') .select('name') .limit(10) const users = await query
Criamos uma consulta de banco de dados que vai até a tabela "user"
e seleciona as dez primeiras entradas e retorna seus nomes. Em teoria, isso funcionaria bem com um Promise
regular.
Mas e se quiséssemos modificar a consulta com base em certas condições, como parâmetros de string de consulta? Seria bom poder continuar modificando a consulta antes de finalmente aguardar o Promise
.
const query = db('user') .select('name') .limit(10) if (orderBy) { query.orderBy(orderBy) } if (limit) { query.limit(limit) } if (id) { query.where({ id: id }) } const users = await query
Se a consulta de banco de dados original fosse uma Promise
padrão, ela instanciaria prontamente a consulta assim que atribuíssemos a variável e não poderíamos modificá-la posteriormente.
Com a avaliação preguiçosa, podemos escrever um código como este que é mais fácil de seguir, melhora a experiência do desenvolvedor e só executa a consulta uma vez quando precisamos dela.
Esse é um exemplo em que a avaliação preguiçosa é ótima. Também pode ser útil para coisas como construir, modificar e orquestrar solicitações HTTP.
Lazy Promise
s são muito legais para os casos de uso certos, mas isso não quer dizer que devam substituir todos os Promise
. Em alguns casos, é bom instanciar rapidamente e ter a resposta pronta o mais rápido possível.
Este é outro daqueles cenários “depende”. Mas da próxima vez que alguém lhe pedir para fazer uma Promise
, considere ser preguiçoso ( ͡° ͜ʖ ͡°).
Muito obrigado pela leitura. Se você gostou deste artigo, por favor
Originalmente publicado em