O objeto Promise representa a conclusão (ou a falha) eventual de uma operação assíncrona e o valor que dela resulta.
Este é um tópico difícil para muitas pessoas, especialmente se você programa em uma linguagem completamente síncrona. Se isso parecer demais para você, ou se você quiser aprender mais sobre concorrência e paralelismo, assista (via go.dev) ou assista direto pelo vimeo e leia os slides da brilhante palestra "Concurrency is not parallelism".
Uma Promise tem três estados:
Quando é criada, uma promise fica pendente. Em algum momento no futuro, ela pode ser resolvida ou rejeitada. Depois que uma promise é resolvida ou rejeitada uma vez, ela nunca mais pode ser resolvida ou rejeitada de novo, e o estado dela não pode mudar.
Em outras palavras:
Uma promise pode ser resolvida de várias formas:
// Creates a promise that is immediately resolved
Promise.resolve(value);
// Creates a promise that is immediately resolved
new Promise((resolve) => {
resolve(value);
});
// Chaining a promise leads to a resolved promise
somePromise.then(() => {
// ...
return value;
});
Nos exemplos acima, value pode ser qualquer coisa, incluindo um erro, undefined, null ou outra promise.
Normalmente, você quer resolver com um valor que não seja um erro.
Uma promise pode ser rejeitada de várias formas:
// Creates a promise that is immediately rejected
Promise.reject(reason)
// Creates a promise that is immediately rejected
new Promise((_, reject) {
reject(reason)
})
// Chaining a promise with an error leads to a rejected promise
somePromise.then(() => {
// ...
throw reason
})
Nos exemplos acima, reason pode ser qualquer coisa, incluindo um erro, undefined ou null.
Normalmente, você quer rejeitar com um erro.
Uma promise pode ser continuada com uma ação futura assim que for resolvida ou rejeitada.
promise.then() é chamado assim que promise for resolvidapromise.catch() é chamado assim que promise for rejeitadapromise.finally() é chamado assim que promise for resolvida ou rejeitadaToda promise é "thenable".
Isso significa que há uma função then disponível que será executada assim que a promise original for resolvida.
Dado promise.then(onResolved), o callback onResolved recebe o valor com o qual a promise original foi resolvida.
Isso sempre retorna uma nova promise "encadeada".
Retornar um value de then resolve a promise "encadeada".
Lançar um reason em then rejeita a promise "encadeada".
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
});
const promise2 = promise1.then(function (value) {
console.log(value);
// expected output: "Success!"
return true;
});
Isso vai imprimir "Success!" depois de aproximadamente 1000 ms.
O estado e o valor de promise1 serão resolved e "Success!".
O estado e o valor de promise2 serão resolved e true.
Há um segundo argumento disponível que roda quando a promise original é rejeitada.
Dado promise.then(onResolved, onRejected), o callback onResolved recebe o valor com o qual a promise original foi resolvida, ou o callback onRejected recebe o motivo pelo qual a promise foi rejeitada.
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
if (Math.random() < 0.5) {
reject('Nope!');
}
});
function log(value) {
console.log(value);
return true;
}
function shout(reason) {
console.error(reason.toUpperCase());
return false;
}
const promise2 = promise1.then(log, shout);
"Success!" depois de aproximadamente 1000 ms.
promise1 serão resolved e "Success!".promise2 serão resolved e true."NOPE!" imediatamente.
promise1 serão rejected e Nope!.promise2 serão resolved e false.É importante entender que, por causa das regras do ciclo de vida, quando ela chama reject, o resolve que chega cerca de 1000 ms depois é silenciosamente ignorado, já que o estado interno não pode mudar depois que ela foi rejeitada ou resolvida.
É importante entender que retornar um valor de uma promise a resolve, e lançar um valor a rejeita.
Quando promise1 é resolvida e há um onResolved encadeado: then(onResolved), essa continuação é uma nova promise que pode ser resolvida ou rejeitada.
Quando promise1 é rejeitada mas há um onRejected encadeado: then(, onRejected), essa continuação é uma nova promise que pode ser resolvida ou rejeitada.
Às vezes você quer capturar erros e só continuar quando a promise original chama reject.
Dado promise.catch(onCatch), o callback onCatch recebe o motivo pelo qual a promise original foi rejeitada.
Isso sempre retorna uma nova promise "encadeada".
Retornar um value de catch resolve a promise "encadeada".
Lançar um reason em catch rejeita a promise "encadeada".
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
if (Math.random() < 0.5) {
reject('Nope!');
}
});
function log(value) {
console.log(value);
return 'done';
}
function recover(reason) {
console.error(reason.toUpperCase());
return 42;
}
const promise2 = promise1.catch(recover).then(log);
Em cerca de metade dos casos, isso vai imprimir "Success!" depois de aproximadamente 1000 ms.
Na outra metade dos casos, isso vai imprimir 42 imediatamente.
promise1 for resolvida, catch é pulado e a execução chega a then, que imprime o valor.
promise1 serão resolved e "Success!".promise2 serão resolved e "done";promise1 for rejeitada, catch é executado, o que retorna um valor, e assim a cadeia agora fica resolved, e a execução chega a then, que imprime o valor.
promise1 serão rejected e "Nope!";promise2 serão resolved e "done";Às vezes você quer executar código depois que uma promise é finalizada, independentemente de ela ser resolvida ou rejeitada.
Dado promise.finally(onSettled), o callback onSettled não recebe nada.
Isso sempre retorna uma nova promise "encadeada".
Retornar um value de finally copia o estado e o valor da promise original, ignorando o value.
Lançar um reason em finally rejeita a promise "encadeada", sobrescrevendo qualquer estado e valor ou motivo da promise original.
Vários dos métodos juntos:
const myPromise = new Promise(function (resolve, reject) {
const sampleData = [2, 4, 6, 8];
const randomNumber = Math.round(Math.random() * 5);
if (sampleData[randomNumber]) {
resolve(sampleData[randomNumber]);
} else {
reject('Sampling did not result in a sample');
}
});
const finalPromise = myPromise
.then(function (sampled) {
// If the random number was 0, 1, 2, or 3, this will be
// reached and the number 2, 4, 6, or 8 will be logged.
console.log(`Sampled data: ${sampled}`);
return 'yay';
})
.catch(function (reason) {
// If the random number was 4 or 5, this will be reached and
// reason will be "An error occurred". The entire chain will
// then reject with an Error with the reason as message.
throw new Error(reason);
})
.finally(function () {
// This will always log after either the sampled data is
// logged or the error is raised.
console.log('Promise completed');
});
randomNumber é 0-3:
myPromise será resolvida com o valor 2, 4, 6, or 8
finalPromise será resolvida com o valor 'yay'
Sampled data: ...Promise completedrandomNumber é 4-5:
myPromise será rejeitada com o motivo 'Sampling did not result in a sample'
finalPromise será rejeitada com o motivo Error('Sampling did not result in a sample')
Promise completed"uncaught rejected promise: Error('Sampling did not result in a sample')"
Como mostrado acima, reject funciona com uma string, e uma promise também pode rejeitar com um Error.
Se encadear promises ou o uso geral não estiver claro, o tutorial no MDN é um bom recurso para consultar.