A API JavaScript Promise é incrível, mas pode se tornar incrível com async
e await
!
Embora o código síncrono seja mais fácil de acompanhar e depurar, o assíncrono geralmente é melhor em termos de desempenho e flexibilidade. Por que “atrasar o show” quando o senhor pode acionar várias solicitações de uma só vez e processá-las quando cada uma estiver pronta? As promessas estão se tornando uma grande parte do mundo JavaScript, com muitas APIs novas sendo implementadas com a filosofia de promessa. Vamos dar uma olhada nas promessas, na API e em como elas são usadas!
Promessas na natureza
A API XMLHttpRequest é assíncrona, mas faz não usam a API Promises. No entanto, existem algumas APIs nativas que agora usam promessas:
As promessas só se tornarão mais predominantes, por isso é importante que todos os desenvolvedores de front-end se acostumem com elas. Também vale a pena observar que o Node.js é outra plataforma para promessas (obviamente, já que a promessa é um recurso central da linguagem).
Testar promessas é provavelmente mais fácil do que o senhor pensa porque setTimeout
pode ser usado como sua “tarefa” assíncrona!
Uso básico de promessas
O new Promise()
deve ser usado apenas para tarefas assíncronas legadas, como o uso do setTimeout
ou XMLHttpRequest
. Uma nova Promise é criada com o new
e a promessa fornece resolve
e reject
para o retorno de chamada fornecido:
var p = new Promise(function(resolve, reject) { // Do an async task async task and then... if(/* good condition */) { resolve('Success!'); } else { reject('Failure!'); } }); p.then(function(result) { /* do something with the result */ }).catch(function() { /* error :( */ }).finally(function() { /* executes regardless or success for failure */ });
Cabe ao desenvolvedor chamar manualmente o resolve
ou reject
no corpo do retorno de chamada com base no resultado da tarefa fornecida. Um exemplo realista seria a conversão de XMLHttpRequest em uma tarefa baseada em promessa:
// From Jake Archibald's Promises and Back: // http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promisifying-xmlhttprequest function get(url) { // Return a new promise. return new Promise(function(resolve, reject) { // Do the usual XHR stuff var req = new XMLHttpRequest(); req.open('GET', url); req.onload = function() { // This is called even on 404 etc // so check the status if (req.status == 200) { // Resolve the promise with the response text resolve(req.response); } else { // Otherwise reject with the status text // which will hopefully be a meaningful error reject(Error(req.statusText)); } }; // Handle network errors req.onerror = function() { reject(Error("Network Error")); }; // Make the request req.send(); }); } // Use it! get('story.json').then(function(response) { console.log("Success!", response); }, function(error) { console.error("Failed!", error); });
Às vezes, o senhor não necessidade para concluir uma tarefa assíncrona dentro da promessa — se for possível que uma ação assíncrona seja executada; no entanto, retornar uma promessa é o melhor para que o senhor possa sempre contar com uma promessa saindo de uma determinada função. Nesse caso, o senhor pode simplesmente chamar Promise.resolve()
ou Promise.reject()
sem usar o new
palavra-chave. Por exemplo:
var userCache = {}; function getUserDetail(username) { // In both cases, cached or not, a promise will be returned if (userCache[username]) { // Return a promise without the "new" keyword return Promise.resolve(userCache[username]); } // Use the fetch API to get the information // fetch returns a promise return fetch('users/' + username + '.json') .then(function(result) { userCache[username] = result; return result; }) .catch(function() { throw new Error('Could not find user: ' + username); }); }
Como uma promessa é sempre retornada, o senhor sempre pode usar a palavra-chave then
e catch
em seu valor de retorno!
então
Todas as instâncias de promessa recebem um then
que permite ao senhor reagir à promessa. A primeira then
recebe o resultado fornecido pelo método callback do resolve()
chamada:
new Promise(function(resolve, reject) { // A mock async action using setTimeout setTimeout(function() { resolve(10); }, 3000); }) .then(function(result) { console.log(result); }); // From the console: // 10
O then
é acionada quando a promessa é resolvida. O senhor também pode encadear then
callbacks de métodos:
new Promise(function(resolve, reject) { // A mock async action using setTimeout setTimeout(function() { resolve(10); }, 3000); }) .then(function(num) { console.log('first then: ', num); return num * 2; }) .then(function(num) { console.log('second then: ', num); return num * 2; }) .then(function(num) { console.log('last then: ', num);}); // From the console: // first then: 10 // second then: 20 // last then: 40
Cada then
recebe o resultado do anterior then
anterior.
Se uma promessa já tiver sido resolvida, mas o then
for chamada novamente, o retorno de chamada será acionado imediatamente. Se a promessa for rejeitada e o senhor chamar then
após a rejeição, o retorno de chamada nunca será chamado.
captura
O catch
callback é executado quando a promessa é rejeitada:
new Promise(function(resolve, reject) { // A mock async action using setTimeout setTimeout(function() { reject('Done!'); }, 3000); }) .then(function(e) { console.log('done', e); }) .catch(function(e) { console.log('catch: ', e); }); // From the console: // 'catch: Done!'
O que o senhor fornece ao reject
fica a critério do senhor. Um padrão frequente é enviar um Error
para o catch
:
reject(Error('Data could not be found'));
finalmente
O recém-introduzido finally
é chamada independentemente de sucesso ou falha:
(new Promise((resolve, reject) => { reject("Nope"); })) .then(() => { console.log("success") }) .catch(() => { console.log("fail") }) .finally(res => { console.log("finally") }); // >> fail // >> finally
Promise.all
Pense nos carregadores de JavaScript: há momentos em que você aciona várias interações assíncronas, mas só quer responder quando todas elas forem concluídas – é aí que o Promise.all
entra em cena. O Promise.all
recebe uma matriz de promessas e dispara um retorno de chamada quando todas elas são resolvidas:
Promise.all([promise1, promise2]).then(function(results) { // Both promises resolved }) .catch(function(error) { // One or more promises was rejected });
Uma maneira perfeita de pensar sobre o Promise.all
é disparar vários AJAX (via fetch
) de uma só vez:
var request1 = fetch('/users.json'); var request2 = fetch('/articles.json'); Promise.all([request1, request2]).then(function(results) { // Both promises done! });
O senhor poderia combinar APIs como fetch
e a API Battery, já que ambas retornam promessas:
Promise.all([fetch('/users.json'), navigator.getBattery()]).then(function(results) { // Both promises done! });
Lidar com a rejeição é, obviamente, difícil. Se alguma promessa for rejeitada, o catch
dispara para a primeira rejeição:
var req1 = new Promise(function(resolve, reject) { // A mock async action using setTimeout setTimeout(function() { resolve('First!'); }, 4000); }); var req2 = new Promise(function(resolve, reject) { // A mock async action using setTimeout setTimeout(function() { reject('Second!'); }, 3000); }); Promise.all([req1, req2]).then(function(results) { console.log('Then: ', results); }).catch(function(err) { console.log('Catch: ', err); }); // From the console: // Catch: Second!
Promise.all
será muito útil à medida que mais APIs passarem a usar promessas.
Promise.race
Promise.race
é uma função interessante — em vez de esperar que todas as promessas sejam resolvidas ou rejeitadas, Promise.race
é acionada assim que qualquer promessa na matriz é resolvida ou rejeitada:
var req1 = new Promise(function(resolve, reject) { // A mock async action using setTimeout setTimeout(function() { resolve('First!'); }, 8000); }); var req2 = new Promise(function(resolve, reject) { // A mock async action using setTimeout setTimeout(function() { resolve('Second!'); }, 3000); }); Promise.race([req1, req2]).then(function(one) { console.log('Then: ', one); }).catch(function(one, two) { console.log('Catch: ', one); }); // From the console: // Then: Second!
Um caso de uso poderia ser o acionamento de uma solicitação para uma fonte primária e uma fonte secundária (caso a primária ou a secundária não estejam disponíveis).
Acostume-se com as promessas
As promessas têm sido um tema muito discutido nos últimos anos (ou nos últimos 10 anos, se o senhor for usuário do Dojo Toolkit) e passaram de um padrão de estrutura JavaScript para um elemento básico da linguagem. Provavelmente, é sensato presumir que o senhor verá a maioria das novas APIs JavaScript sendo implementadas com um padrão baseado em promessa…
…e isso é ótimo! Os desenvolvedores podem evitar o inferno do callback e as interações assíncronas podem ser transmitidas como qualquer outra variável. As promessas levam algum tempo para se acostumar, mas as ferramentas estão (nativamente) lá e agora é a hora de aprendê-las!