Você provavelmente já ouviu falar por aí sobre como usar promises é o futuro. Todos os caras bacanas estão usando, mas você não vê o que o torna tão especial. Por que não usar apenas um callback? Qual é a grande coisa nisso tudo? Neste artigo, vamos ver do que trata o promises e como você pode usá-lo para escrever melhor seu código JavaScript.
Promises oferece uma leitura fácil
Vamos dizer que você queira pegar alguns dados da API HipsterJesus e adicionar ao seu site. Essa API responde com dados que se parecem com isto:
{ "text": "<p>Lorem ipsum...</p>", "params": { "paras": 4, "type": "hipster-latin" } }
Se você fosse usar um callback, escreveria algo assim:
$.getJSON('http://hipsterjesus.com/api/', function(data) { $('body').append(data.text); });
Se você tem experiência com jQuery, vai reconhecer que estamos fazendo uma requisição GET e esperando um JSON na resposta. Também estamos passando uma função callback que pega a resposta JSON e a adiciona ao documento.
Outra forma de escrever isso é usando o objeto promise retornado pelo método getJSON. Você pode adicionar um callback diretamente a esse objeto.
var promise = $.getJSON('http://hipsterjesus.com/api/'); promise.done(function(data) { $('body').append(data.text); });
Assim como no exemplo do callback, isso anexa o resultado da requisição da API ao documento quando a requisição obtém sucesso. Mas o que acontece se a requisição falha? Também podemos anexar um handler fail ao nosso promise.
var promise = $.getJSON('http://hipsterjesus.com/api/'); promise.done(function(data) { $('body').append(data.text); }); promise.fail(function() { $('body').append('<p>Oh no, something went wrong!</p>'); });
A maioria das pessoas remove a variável promise, o que torna isso um pouco mais fácil para falar o que o código faz em apenas uma olhada.
$.getJSON('http://hipsterjesus.com/api/') .done(function(data) { $('body').append(data.text); }) .fail(function() { $('body').append('<p>Oh no, something went wrong!</p>'); });
O jQuery também inclui um handler de evento always que é chamado independentemente de a requisição ter sucesso ou falhar.
$.getJSON('http://hipsterjesus.com/api/') .done(function(data) { $('body').append(data.text); }) .fail(function() { $('body').append('<p>Oh no, something went wrong!</p>'); }) .always(function() { $('body').append('<p>I promise this will always be added!.</p>'); });
Com promises, a ordem dos callbacks é respeitada. Estamos garantidos de ter nosso callback done primeiro, então o callback fail, e finalmente o callback always.
Melhores APIs
Suponha que você queira crair um objeto wrapper para a API HipsterJesus. Você adiciona o método, html, para retornar os dados HTML que venham da API. Em vez de esse método pegar o handler que é chamado quando a requisição é resolvida, é possível apenas fazer com que o método retorne um objeto promise.
var hipsterJesus = { html: function() { return $.getJSON('http://hipsterjesus.com/api/').then(function(data) { return data.text; }); } };
O mais legal de tudo isso é que podemos distribuir o objeto promise sem nos preocuparmos com quando ou como ele vai resolver seus valores. Qualquer código que precise retornar valor do promise pode registrar o callback com o done.
O método then permite que se modifique o resultado de um promise e o passe para o próximo handler na cadeia. Isso significa que podemos usar nossa nova API da seguinte forma:
hipsterJesus.html().done(function(html) { $("body").append(html); });
Até recentemente, uma das melhores features do AngularJS era que os templates podiam ser vinculados diretamente com o promises. Em um controller Angular, seria assim:
$scope.hipsterIpsum = $http.get('http://hipsterjesus.com/api/');
Então, seria tão simples como escrever {{ hipsterIpsum.text }}
em um template. Quando o promise resolvesse, Angular faria o update da view automaticamente. Infelizmente, o time do Angular desaprovou essa feature. Por enquanto, isso pode ser habilitado ao chamar $parseProvider.unwrapPromises(true)
. Eu espero que o pessoal do Angular e de outros frameworks incluam essa feature no futuro (viu, Ember?).
Encadeamento
A melhor parte do promises é que você pode fazer um encadeamento! Digamos que você queira adicionar à nossa API um método que retorna um array de parágrafos:
var hipsterJesus = { html: function() { return $.getJSON('http://hipsterjesus.com/api/').then(function(data) { return data.text; }); }, paragraphs: function() { return this.html().then(function(html) { return html.replace(/<[^>]+>/g, "").split(""); }); } };
Nosso método HTML ficou o mesmo, e o estamos usando no método paragraphs. Como o valor seguinte de um callback do promise é passado para o próximo callback na cadeia, ficamos livres para criar métodos pequenos e funcionais que mudam os dados à medida que é passado por eles.
Podemos encadear promises quantas vezes quisermos. Vamos adicionar um método para as sentenças.
var hipsterJesus = { html: function() { return $.getJSON('http://hipsterjesus.com/api/').then(function(data) { return data.text; }); }, paragraphs: function() { return this.html().then(function(html) { return html.replace(/<[^>]+>/g, "").split(""); }); }, sentences: function() { return this.paragraphs().then(function(paragraphs) { return [].concat.apply([], paragraphs.map(function(paragraph) { return paragraph.split(/. /); })); }); } };
Chamadas múltiplas
Provavelmente uma das features mais notáveis do promises é a habilidade para combinar múltiplas chamadas de APIs. Quando usamos callbacks, o que acontece se você precisar fazer duas chamadas à API de uma só vez? Provavelmente você vai acabar escrevendo algo assim:
var firstData = null; var secondData = null; var responseCallback = function() { if (!firstData || !secondData) return; // do something } $.get("http://example.com/first", function(data) { firstData = data; responseCallback(); }); $.get("http://example.com/second", function(data) { secondData = data; responseCallback(); });
Com promises, isso se torna muito mais fácil:
var firstPromise = $.get("http://example.com/first"); var secondPromise = $.get("http://example.com/second"); $.when(firstPromise, secondPromise).done(function(firstData, secondData) { // do something });
Aqui estamos usando o método when para anexar um handler que é chamado quando as duas requisições estão feitas.
Conclusão
É isso! Espero que você tenha conseguido vislumbrar as coisas maravilhosas que pode fazer com promises. Qual a sua forma favorita de usar? Conte nos comentários!
*Nota: Para tornar mais simples, este artigo está usando uma implementação jQuery deferred. Há algumas diferenças entre o objeto jQuery Deferred
e o Promises/A+ epecificação, que é um padrão mais canônico. Para mais informações, veja a a nossa wiki Coming from jQuery.
***
Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em http://davidwalsh.name/write-javascript-promises