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 thenanterior.
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!