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!