DEV Community

loading...
Cover image for This I Promise You - Entendendo o fluxo de Promise em JS

This I Promise You - Entendendo o fluxo de Promise em JS

Thiago Pederzolli Machado da Silva
UI Developer at POSSIBLE and Mentor at Space Squad/Rocketseat
・11 min read

Hoje vim falar sobre promises. As promises surgem como uma alternativa para evitar o uso de muitas callbacks, que poderiam deixar o código pouco legível e para facilitar desenvolvimento de aplicações que precisem fazer requisições API.

Entender Promises é fundamental para esse fluxo de desenvolvimento, porque é em Promises que o fluxo de requisições para uma API funciona. Sua aplicação faz uma requisição à API externa e essa requisição retornará uma Promise, um objeto com diversas propriedades, sendo duas delas, o sucesso e o erro.

API:

API é uma forma de permitir uma aplicação se comunicar com outra, garantindo que uma aplicação que faça uma requisição possa acessar os dados de um determinado banco e trabalhar esses dados da forma que lhe agradar para exibir em sua aplicação.

Pense no fluxo do Ifood, existe o aplicativo e em algum lugar está armazenado as informações sobre os produtos de um determinado restaurante, quando você clica no restaurante, ele faz uma requisição para a API que faz a comunicação com o banco de dados e retorna a Promise dessa requisição. O restaurante existindo nesse banco de dados, o Ifood lida com a propriedade sucesso dessa Promise e nessa propriedade encontram-se as informações referentes aos pratos do restaurante.

Promises:

Uma promise possui três estados, pending, resolved, rejected. Pending é seu estágio inicial após a função que a chamou ser executada. Essa função que chamou a promise continuará sua execução até que a promise retorne algo.

Essa é um dos principais motivos de usarmos tanto promises, o aguardo por seu retorno não trava a aplicação, o que não depende de seu retorno segue sendo executado e o que depende vai para uma área especial, aguardando o retorno da promise para ser executado.

Resolved e Rejected referem-se ao seu retorno. Resolved é o estado de sucesso na requisição, rejected é quando houve algum problema que a Promise retorna um erro.

Com promises, você garante que as callbacks dela nunca serão chamadas antes de acabar a fila atual de execuções, que as callbacks chamadas posteriormente serão executadas de acordo com o fluxo, depois da conclusão da promise e que você pode fazer um encadeamento de callbacks para determinadas ações com o que for retornado da promise.

Construção de uma Promise:

Ela é feita a partir de um Constructor. Constructor nada mais é que uma função especial construtora de objetos. E sim, Promise nada mais é que um objeto com duas propriedades: resolve e reject.

Sua sintaxe é simples:

const promise = new Promise((resolve,reject) => {

}
Enter fullscreen mode Exit fullscreen mode

O new é um operador que garante que estaremos criando um novo objeto baseado na função passada após o operador, logo, nessa sintaxe acima, estamos definindo que a variável promise irá armazenar um novo objeto baseado no consructor Promise.

Resolve e Reject:

Resolve e Reject são duas funções de retorno de uma promise. Serão as funções a serem executadas de acordo com o retorno da execução da promise. Se uma promise satisfaz a condição desejada, ela foi resolvida, logo será executado o que for passado para a função resolve, caso a condição não seja satisfeita, será rejeitada e será então executado o que for passado para a função reject.

Quer ver funcionando?

Abra o seu VSCode e estruture a seguinte linha de raciocínio:

const promise = (number) => {
  new Promise((resolve, reject) => {
    const numberInput = number;

    if(numberInput > 10) {
      return resolve(console.log('Excelente, promise resolvida com sucesso'));
    }
    return reject(console.log('Ops! Ocorreu algum erro com sua requisição, por favor, tente novamente.'));
  });
}
Enter fullscreen mode Exit fullscreen mode

Entendendo o código acima:

  • promise é uma função que espera um argumento ao ser chamado, esse argumento refere-se ao parâmetro number e retorna uma promise.
  • O valor desse number é armazenado na variável numberInput.
  • O if verifica a condição do numberInput, se ele for maior que 10.
  • Caso o numberInput seja maior que 10, a promise será resolvida e terá o retorno no console a frase “Excelente, promise resolvida com sucesso”.
  • Caso o numberInput seja menor que 10, a promise será rejeitada e terá o retorno no console a frase “Ops! Ocorreu algum erro com a sua requisição, por favor, tente novamente”.

Chame a função passando um valor maior que 10 como argumento:

promise(12)
Enter fullscreen mode Exit fullscreen mode

Agora rode novamente o comando no seu terminal:

node script.js
Enter fullscreen mode Exit fullscreen mode

Ao rodar o comando, você verá que no seu terminal exibirá a seguinte mensagem:

“Excelente, promise resolvida com sucesso”
Enter fullscreen mode Exit fullscreen mode

Altere o valor de chamada da função para um número menor que 10 e rode o comando novamente:

promise(2)
Enter fullscreen mode Exit fullscreen mode
node script.js
Enter fullscreen mode Exit fullscreen mode
“Ops! Ocorreu algum erro com sua requisição, por favor, tente novamente”
Enter fullscreen mode Exit fullscreen mode

Dessa vez o retorno foi diferente, pois não foi satisfeita a condição para que sua requisição fosse bem sucedida.

Para ter uma perspectiva melhor do fluxo de uma promise, execute testes com o Math.random().

const randomNumber = Math.floor(Math.random() * 20)
promise(randomNumber);
Enter fullscreen mode Exit fullscreen mode

fetch API:

fetch() é a função que é executada para fazer a requisição a uma API. Ela é uma função nativa do objeto Window(do seu navegador, no caso) que recebe como argumento o endpoint(termo utilizado para se referir ao link que deve ser utilizado para fazer a requisição para a API) e retorna a promise, resolvida ou não.

Normalmente esse retorno pode vir de várias formas, aqui vamos trabalhar com API de retornos JSON, que é algo muito similar a um objeto JavaScript, mas para manipularmos precisará de um pequeno tratamento.

.then() e .catch():

.then() e .catch() são as primeiras formas que tivemos de lidar com o retorno de promises. Lembrando que promises são assíncronas, mas o fluxo do nosso código não é, precisavamos de alguma forma de informar ao código que naquele bloco de código precisaria haver uma espera por uma resposta.

Para isso foi desenvolvido o .then(), ele faz parte de uma cadeia de encadeamento de funções, serve para tratar o retorno resolvido da promise através de uma callback functions. Em tradução literal, “then” significa “então”, logo na cadeia de funções, podemos entender, com ele vindo após o fetch que a lógica será “faça a requisição, espere o retorno e então execute esse código aqui”.

Já o .catch() serve para tratar o retorno rejeitado da promise, caso haja alguma falha no processo de requisição e ela retorne um erro, o catch, captura esse erro para que você possa utilizar a mensagem de erro para tentar entender qual foi a falha ou em uma aplicação gerar alguma notificação para a pessoa usuária de porque sua solicitação não retornou algo.

Vamos entender juntos esse fluxo, abra seu VScode, crie um arquivo script.js em outra pasta e prepare seu ambiente para que possas trabalhar com fetch direto no VSCode:

Você precisará trabalhar com uma dependência do node, chamada node-fetch. Pois, como comentado na descrição do fetchAPI, ela é nativa do Objeto Window, ou seja, funciona no ambiente de navegador. Essa dependência que vamos instalar irá permitir utilizar o fetch no contexto do node.js e verificar seu retorno no próprio VSCode.

Para começar, precisamos iniciar um arquivo package.json que irá armazenar as informações da dependência e permitir seu uso, para isso, rode o comando abaixo:

npm init -y
Enter fullscreen mode Exit fullscreen mode

Feito esse procedimento, prossiga para instalação do node-fetch com o comando:

npm install node-fetch
Enter fullscreen mode Exit fullscreen mode

Excelente! Agora seu ambiente está pronto para os primeiros experimentos com fetch. Para essa prática de fixação, utilizaremos uma API gratuita de citações da franquia star wars que retorna elas de forma aleatória:

http://swquotesapi.digitaljedi.dk/api/SWQuote/RandomStarWarsQuote
Enter fullscreen mode Exit fullscreen mode

No site acima, se copiado no seu navegador, estará o retorno que receberemos ao final do .then() que irá tratar o formato do retorno do fetch. Para uma melhor visualização, indico a extensão do chrome JSON Viewer, que irá garantir a estrutura para visualizar no formato adequado:

Será exibido no formato acima caso seja instalada a extensão JSON Viewer.

Sua primeira linha terá que solicitar a dependência, para atribuir um valor a fetch e tornar viável seu uso, logo seu código deve começar com:

const fetch = require('node-fetch');
Enter fullscreen mode Exit fullscreen mode

Feito isso, podemos desenvolver a função:

function SWQuote() {
    fetch('http://swquotesapi.digitaljedi.dk/api/SWQuote/RandomStarWarsQuote')
    .then((quotes) => console.log(quotes))
}

SWQuotes()
Enter fullscreen mode Exit fullscreen mode

Ao rodar o comando node script.js, teremos um objeto similar ao seguinte objeto retornado no console:

Response {
  size: 0,
  timeout: 0,
  [Symbol(Body internals)]: {
    body: Gunzip {
      _writeState: [Uint32Array],
      _readableState: [ReadableState],
      readable: true,
      _events: [Object: null prototype],
      _eventsCount: 6,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: true,
      allowHalfOpen: true,
      _transformState: [Object],
      _hadError: false,
      bytesWritten: 0,
      _handle: [Zlib],
      _outBuffer: <Buffer 7b 22 69 64 22 3a 35 34 2c 22 73 74 61 72 57 61 72 73 51 75 6f 74 65 22 3a 22 52 65 6d 65 6d 62 65 72 2c 20 6d 79 20 64 65 61 72 20 4f 62 69 2d 57 61 ... 16334 more bytes>,
      _outOffset: 0,
      _chunkSize: 16384,
      _defaultFlushFlag: 2,
      _finishFlushFlag: 2,
      _defaultFullFlushFlag: 3,
      _info: undefined,
      _level: -1,
      _strategy: 0,
      [Symbol(kCapture)]: false
    },
    disturbed: false,
    error: null
  },
  [Symbol(Response internals)]: {
    url: 'http://swquotesapi.digitaljedi.dk/api/SWQuote/RandomStarWarsQuote',
    status: 200,
    statusText: 'OK',
    headers: Headers { [Symbol(map)]: [Object: null prototype] },
    counter: 0
  }
}
Enter fullscreen mode Exit fullscreen mode

Agora entra a questão que havia comentado sobre o tipo de retorno da requisição. Dessa forma, ela não foi tratada ainda. Mas não se preocupe, não há necessidade de entender esse objeto agora e muito menos se desesperar com essa ideia de uma lógica para tratar essa informação e obter o que desejamos. Felizmente, como realmente esse tipo de processo é rotineiro nos dias de uma pessoa desenvolvedora, já existe uma funcionalidade para isso, que é a função .json().

Vamos evoluir nosso código para termos exatamente a resposta que desejamos, tratando esse primeiro objeto:

function SWQuote() {
    fetch('http://swquotesapi.digitaljedi.dk/api/SWQuote/RandomStarWarsQuote')
  .then((quotes) => quotes.json())
  .then((quote) => console.log(quote))
}

SWQuotes()
Enter fullscreen mode Exit fullscreen mode

Executando mais uma vez o comando node script.js, agora você terá um retorno similar ao abaixo, dado que a API retorna citações de forma aleatória, então pode não ser a mesma citação.

{
  id: 6,
  starWarsQuote: 'It’s the ship that made the Kessel run in less than twelve parsecs. I’ve outrun Imperial starships. Not the local bulk cruisers, mind you. I’m talking about the big Corellian ships, now. She’s fast enough for you, old man. — Han Solo',
  faction: 4
}
Enter fullscreen mode Exit fullscreen mode

Para entender então o que fizemos:

  • fetch() faz a requisição à API e retorna a promise com seu resolve e reject.
  • O primeiro .then() pega esse retorno e faz o tratamento para json.
  • O segundo .then() pega o retorno do tratamento e nos exibe no console como fica.

Ficou faltando agora lidarmos com algum possível erro ao longo do processo de requisição. Fazemos isso com a função .catch(), que será sempre a última função no nosso encadeamento de funções, justamente para capturar o erro ao fim do processo e permitir que você possa manipulá-lo como achar mais adequado.

function SWQuote() {
    fetch('http://swquotesapi.diitaljedi.dk/api/SWQuote/RandomStarWarsQuote')
    .then((quotes) => quotes.json())
    .then((quote) => console.log(quote))
    .catch((error) => console.log(error))
}
Enter fullscreen mode Exit fullscreen mode

No código acima, perceba que foi feita uma leve alteração no link para a API, apagando o g em “digital” para forçar um erro. Abaixo, perceba a diferença da mensagem com o .catch() para informar o erro e sem o .catch():

// com o .catch((error) => console.log(error))
FetchError: request to http://swquotesapi.diitaljedi.dk/api/SWQuote/RandomStarWarsQuote failed, reason: getaddrinfo ENOTFOUND swquotesapi.diitaljedi.dk
    at ClientRequest.<anonymous> (/media/thiago-troll/5C112506248A591C/trybe/trybe-exercises/modulo_intro_web/bloco_9/dia_2/fetch_API/node_modules/node-fetch/lib/index.js:1461:11)
    at ClientRequest.emit (events.js:315:20)
    at Socket.socketErrorListener (_http_client.js:426:9)
    at Socket.emit (events.js:315:20)
    at emitErrorNT (internal/streams/destroy.js:92:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:60:3)
    at processTicksAndRejections (internal/process/task_queues.js:84:21) {
  type: 'system',
  errno: 'ENOTFOUND',
  code: 'ENOTFOUND'
}
Enter fullscreen mode Exit fullscreen mode
// sem o .catch((error) => console.log(error))
(node:117135) UnhandledPromiseRejectionWarning: FetchError: request to http://swquotesapi.diitaljedi.dk/api/SWQuote/RandomStarWarsQuote failed, reason: getaddrinfo ENOTFOUND swquotesapi.diitaljedi.dk
    at ClientRequest.<anonymous> (/media/thiago-troll/5C112506248A591C/trybe/trybe-exercises/modulo_intro_web/bloco_9/dia_2/fetch_API/node_modules/node-fetch/lib/index.js:1461:11)
    at ClientRequest.emit (events.js:315:20)
    at Socket.socketErrorListener (_http_client.js:426:9)
    at Socket.emit (events.js:315:20)
    at emitErrorNT (internal/streams/destroy.js:92:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:60:3)
    at processTicksAndRejections (internal/process/task_queues.js:84:21)
(node:117135) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:117135) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Enter fullscreen mode Exit fullscreen mode

E você pode tratar ainda mais esse erro, altere seu código para:

function SWQuote() {
    fetch('http://swquotesapi.diitaljedi.dk/api/SWQuote/RandomStarWarsQuote')
    .then((quotes) => quotes.json())
    .then((quote) => console.log(quote))
    .catch((error) => console.log(error.message))
}
Enter fullscreen mode Exit fullscreen mode

Ao rodar o comando, você receberá um retorno similar a este:

request to http://swquotesapi.diitaljedi.dk/api/SWQuote/RandomStarWarsQuote failed, reason: getaddrinfo ENOTFOUND swquotesapi.diitaljedi.dk
Enter fullscreen mode Exit fullscreen mode

async/await:

Com a evolução da linguagem, a partir do ES2017, surge o async/await. Que é uma sintaxe mais confortável de se trabalhar com requisições assíncronas, ela acaba com essa verbosidade da cadeia de vários .then() e permite uma sintaxe por linhas, deixando a legibilidade do código mais agradável.

Podemos pegar a função desenvolvida acima com .then() e refatorá-la para async/await, ficando da seguinte maneira:

async function SWQuotes() {
    const quotes = await fetch('http://swquotesapi.digitaljedi.dk/api/SWQuote/RandomStarWarsQuote');
    const quote = await quotes.json();
    console.log(quote);
}

SWQuotes()
Enter fullscreen mode Exit fullscreen mode

Você pode rodar o node script.js e verá que o retorno continua similar ao que tinha antes:

{
  id: 12,
  starWarsQuote: 'I sense something. A presence I have not felt since…. (A New Hope) - Darth Vader',
  faction: 1
}
Enter fullscreen mode Exit fullscreen mode

Entendendo o código acima:

  • A declaração async antes da função informa ao script que a função que vem a seguir é de caráter assíncrono e irá aguardar o retorno de algo para prosseguir com sua execução.
  • A declaração de await antes do fetch() e do tratamento do JSON tem o mesmo significado. Em tradução livre, await pode significar aguardar. O que ele faz é informar ao código que, para atribuir o valor a variável quotes e quote, ele deve aguardar a resolução do que está sendo realizado após o await.

Agora você deve estar se perguntando como lidar com erros com essa sintaxe. Ai que entra um outro bloco que é o try/catch. É uma sintaxe onde o try vai ser responsável pelo sucesso e o catch segue responsável pela captura do erro.

async function SWQuotes() {
    try {
        const quotes = await fetch('http://swquotesapi.digitaljedi.dk/api/SWQuote/RandomStarWarsQuote');
        const quote = await quotes.json();
  return console.log(quote);
} catch (error) {
        return console.log(error.message);
    }
}

SWQuotes();
Enter fullscreen mode Exit fullscreen mode

Ao executar esse código, você irá obter sucesso na requisição e terá como retorno uma mensagem similar a de quando usou o .then() ou quando usou o async/await sem o try/catch, que será:

{
  id: 53,
  starWarsQuote: 'An object cannot make you good or evil. The temptation of power, forbidden knowledge, even the desire to do good can lead some down that path. But only you can change yourself. — Bendu',
  faction: 2
}
Enter fullscreen mode Exit fullscreen mode

Agora se repetirmos o mesmo erro do exemplo da parte do conteúdo de .catch(), apagando o g em digital, o código ficará assim:

async function SWQuotes() {
    try {
        const quotes = await fetch('http://swquotesapi.diitaljedi.dk/api/SWQuote/RandomStarWarsQuote');
        const quote = await quotes.json();
  return console.log(quote);
} catch (error) {
        return console.log(error.message);
    }
}

SWQuotes();
Enter fullscreen mode Exit fullscreen mode

Teremos o mesmo retorno de quando usamos o .catch() encadeado com as funções .then():

request to http://swquotesapi.diitaljedi.dk/api/SWQuote/RandomStarWarsQuote failed, reason: getaddrinfo ENOTFOUND swquotesapi.diitaljedi.dk
Enter fullscreen mode Exit fullscreen mode

Espero ter ajudadao a compreender melhor um fluxo tão importante no nosso cenário. Mas também entenda que será algo constante nos seus dias, logo, se não compreendeu totalmente, a prática no seu dia-a-dia vai lhe ajudar a absorver o conceito.

Discussion (0)