Eu já cobri o básico das promessas de JavaScript e como usar as palavras -chave assíncronas/aguardar para simplificar seu código assíncrono existente. Este artigo é uma visão mais avançada das promessas de JavaScript. Exploraremos quatro maneiras comuns promete a viagem desenvolvedores e truques para resolvê -los.
Gotcha #1: Promise os manipuladores de retorno promessas
Se você está retornando informações de um then
ou catch
Manipulador, ele sempre será envolvido em uma promessa, se ainda não for uma promessa. Então, você nunca precisa escrever código como este:
firstAjaxCall.then(() => {
return new Promise((resolve, reject) => {
nextAjaxCall().then(() => resolve());
});
});
Desde nextAjaxCall
Também retorna uma promessa, você pode fazer isso: em vez disso:
firstAjaxCall.then(() => {
return nextAjaxCall();
});
Além disso, se você estiver retornando um valor simples (não promessa), o manipulador retornará uma promessa resolvida para esse valor, para que você possa continuar ligando then
Nos resultados:
firstAjaxCall.then((response) => {
return response.importantField
}).then((resolvedValue) => {
// resolvedValue is the value of response.importantField returned above
console.log(resolvedValue);
});
Tudo isso é muito conveniente, mas e se você não souber o estado de um valor recebido?
Truque nº 1: use promey.resolve () para resolver valores recebidos
Se você não tiver certeza se o seu valor recebido já é uma promessa, você pode simplesmente usar o método estático Promise.resolve()
. Por exemplo, se você obtiver uma variável que pode ou não ser uma promessa, basta passar como um argumento para Promise.resolve
. Se a variável for uma promessa, o método retornará a promessa; Se a variável for um valor, o método retornará uma promessa resolvida para o valor:
let processInput = (maybePromise) => {
let definitelyPromise = Promise.resolve(maybePromise);
definitelyPromise.then(doSomeWork);
};
Pegue #2:. Então sempre leva uma função
Você provavelmente já viu (e possivelmente escrito) código de promessa que se parece com o seguinte:
let getAllArticles = () => {
return someAjax.get('/articles');
};
let getArticleById = (id) => {
return someAjax.get(`/articles/${id}`);
};
getAllArticles().then(getArticleById(2));
A intenção do código acima é obter todos os artigos primeiro e depois, quando for feito, obtenha o Article
com o ID de 2. Embora possamos ter desejado Uma execução seqüencial, o que está acontecendo é que essas duas promessas estão sendo iniciadas essencialmente ao mesmo tempo, o que significa que eles poderiam concluir em qualquer ordem.
A questão aqui é que falhamos em aderir a uma das regras fundamentais do JavaScript: que os argumentos às funções são sempre avaliados antes de serem passados para a função. O .then
não está recebendo uma função; está recebendo o valor de retorno de getArticleById
. Isso é porque estamos ligando getArticleById
imediatamente com o operador parênteses.
Existem algumas maneiras de consertar isso.
Trick #1: Enrole a chamada em uma função de seta
Se você quisesse suas duas funções processadas sequencialmente, você pode fazer algo assim:
// A little arrow function is all you need
getAllArticles().then(() => getArticleById(2));
Embalando a chamada para getArticleById
Em uma função de seta, fornecemos .then
com uma função que pode chamar quando getAllArticles()
resolveu.
Truque nº 2: passe em funções nomeadas para. Então
Você nem sempre precisa usar funções anônimas embutidas como argumentos para .then
. Você pode atribuir facilmente uma função a uma variável e passar a referência a essa função para .then
em vez de.
// function definitions from Gotcha #2
let getArticle2 = () => {
return getArticleById(2);
};
getAllArticles().then(getArticle2);
getAllArticles().then(getArticle2);
Nesse caso, estamos apenas passando na referência à função e não a chamando.
Truque #3: use async/aguarde
Outra maneira de tornar a ordem dos eventos mais clara é usar o async/await
Palavras -chave:
async function getSequentially() {
const allArticles = await getAllArticles(); // Wait for first call
const specificArticle = await getArticleById(2); // Then wait for second
// ... use specificArticle
}
Agora, o fato de darmos dois passos, cada um seguindo o outro, é explícito e óbvio. Não prosseguimos com execução até que ambos sejam concluídos. Esta é uma excelente ilustração da clareza await
fornece quando consumir promessas.
Gotcha #3: Argumentos não funcionais.
Agora vamos tomar o Gotcha #2 e adicionar um pouco de processamento extra ao final da corrente:
let getAllArticles = () => {
return someAjax.get('/articles');
};
let getArticleById = (id) => {
return someAjax.get(`/articles/${id}`);
};
getAllArticles().then(getArticleById(2)).then((article2) => {
// Do something with article2
});
Já sabemos que essa cadeia não funcionará sequencialmente como queremos, mas agora descobrimos algum comportamento peculiar na Promiseland. O que você acha que é o valor de article2
no último .then
?
Já que não estamos passando uma função para o primeiro argumento de .then
JavaScript passa na promessa inicial com seu valor resolvido, portanto o valor de article2
é o que quer que seja getAllArticles()
resolveu. Se você tem uma longa cadeia de .then
métodos e alguns de seus manipuladores estão obtendo valores de antecipadamente .then
métodos, verifique se você está realmente passando em funções para .then
.
Trick #1: Passe as funções nomeadas com parâmetros formais
Uma maneira de lidar com isso é passar nas funções nomeadas que definem um único parâmetro formal (ou seja, assumir um argumento). Isso nos permite criar algumas funções genéricas que podemos usar em uma cadeia de .then
métodos ou fora da cadeia.
Digamos que temos uma função, getFirstArticle
isso faz uma chamada de API para obter o mais novo article
em um conjunto e resolve para um article
Objeto com propriedades como ID, título e data de publicação. Então diga que temos outra função, getCommentsForArticleId
isso pega um ID do artigo e faz uma chamada de API para obter todos os comentários associados a esse artigo.
Agora, tudo o que precisamos para conectar as duas funções é obter do valor de resolução da primeira função (um article
objeto) para o valor de argumento esperado da segunda função (um ID do artigo). Nós poderia Use uma função embutida anônima para esse fim:
getFirstArticle().then((article) => {
return getCommentsForArticleId(article.id);
});
Ou, poderíamos criar uma função simples que pegue um artigo, retorna o ID e aciona tudo junto com .then
:
let extractId = (article) => article.id;
getFirstArticle().then(extractId).then(getCommentsForArticleId);
Essa segunda solução obscurece um valor de resolução de cada função, pois não são definidos em linha. Mas, por outro lado, cria algumas funções flexíveis que provavelmente poderíamos reutilizar. Observe, também, que estamos usando o que aprendemos com o primeiro Gotcha: embora extractId
não retorna uma promessa, .then
envolverá seu valor de retorno em uma promessa, o que nos permite ligar .then
de novo.
Truque nº 2: use async/aguarde
Outra vez, async/await
pode ser resgatado, tornando as coisas mais óbvias:
async function getArticleAndComments() {
const article = await getFirstArticle();
const comments = await getCommentsForArticleId(article.id); // Extract ID directly
// ... use comments
}
Aqui, simplesmente esperamos por getFirstArticle()
Para terminar, use o artigo para obter o ID. Podemos fazer isso porque sabemos ao certo que o artigo foi resolvido pela operação subjacente.
Gotcha #4: quando assíncrono/aguarda estraga sua simultaneidade
Digamos que você queira iniciar várias operações assíncronas de uma só vez, então você as coloca em um loop e usa await
:
// (Bad practice below!)
async function getMultipleUsersSequentially(userIds) {
const users = ();
const startTime = Date.now();
for (const id of userIds) {
// await pauses the *entire loop* for each fetch
const user = await fetchUserDataPromise(id);
users.push(user);
}
const endTime = Date.now();
console.log(`Sequential fetch took ${endTime - startTime}ms`);
return users;
}
// If each fetch takes 1.5s, 3 fetches would take ~4.5s total.
Neste exemplo, o que nós querer é enviar tudo isso fetchUserDataPromise()
solicitações juntas. Mas o que nós pegar Cada um deles ocorre sequencialmente, o que significa que o loop aguarda que cada um seja concluído antes de continuar para o próximo.
Truque nº 1: use Promise.All
Resolver este é simples com Promise.all
:
// (Requests happen concurrently)
async function getMultipleUsersConcurrently(userIds) {
console.log("Starting concurrent fetch...");
const startTime = Date.now();
const promises = userIds.map(id => fetchUserDataPromise(id));
const users = await Promise.all(promises);
const endTime = Date.now();
console.log(`Concurrent fetch took ${endTime - startTime}ms`);
return users;
}
// If each fetch takes 1.5s, 3 concurrent fetches would take ~1.5s total (plus a tiny overhead).
Promise.all
diz para tomar todo o Promises
Na matriz e inicie -os de uma só vez, espere até que todos tenham concluído antes de continuar. Neste caso de uso, as promessas são a abordagem mais simples do que async/await
. (Mas observe que ainda estamos usando await
para esperar Promise.all
para concluir.)
Conclusão
Embora muitas vezes possamos usar async/await
Para resolver problemas nas promessas, é fundamental entender as promessas para realmente entender o que o async/await
Palavras -chave estão fazendo. Os Gotchas destinam -se a ajudá -lo a entender melhor como as promessas funcionam e como usá -las efetivamente em seu código.