da2k / curso-javascript-ninja

Curso Javascript Ninja
http://blog.da2k.com.br/curso-javascript-ninja/
2.35k stars 2.94k forks source link

[Aula #13] - map, filter e desafio da semana #13 #9912

Closed pgstudies22 closed 2 years ago

pgstudies22 commented 2 years ago

Olá professor, tudo bem?

Callbacks dos métodos map, filter e reduce devem ser funções puras?

Pergunto pq sempre que vejo alguém executando efeito colateral em algum callback desses métodos, parece que tem algo estranho. Que o método não deveria ser usado para isso.

Mas ao mesmo tempo, não encontrei na doc desses métodos no MDN algo que sustente a tese de que deveriam ser funções puras.

Na doc do map, por exemplo, é falado que ele não deve ser usado quando o array que ele retorna não é aproveitado. Mas já vi por aí callback em que há mutação em variável/propriedade externa antes do return do item que será adicionado no array que o map gera...

@fdaciuk

fdaciuk commented 2 years ago

Oi @pgstudies22! O ideal é que sim, sejam funções puras. Isso vai evitar bugs difíceis de achar e dores de cabeça de maneira geral xD Claro que você pode fazer mutação local ali, se achar mais fácil, desde que não modifique os objetos originais: crie sempre novos e modifique eles, se for o caso :)

Um outro caso para o map principalmente é também quando você precisa fazer uma chamada async com cada item do array: a função pode retornar uma Promise (pode usar async/await, inclusive), mas tem sempre que lembrar que o map vai sempre retornar antes das Promises serem resolvidas.

Então se vc precisa fazer uma ação async com cada item de um array, pode usar o map, mas você vai precisar usar um Promise.all ou Promise.allSettled, passando o resultado do map, para aguardar todas as chamadas async terminarem corretamente antes de fazer alguma coisa com o resultado, já que o map é síncrono :)

pgstudies22 commented 2 years ago

Um outro caso para o map principalmente é também quando você precisa fazer uma chamada async com cada item do array...

Seria mais ou menos isso?

const pokemonPromises = Array
  .from({ length: 3 })
  .map(async (_, index) => {
    const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${index + 1}`)
    return res.json()
  })

Promise
  .allSettled(pokemonPromises)
  .then(console.log) // [{status: 'fulfilled', value: {…}}, ...]
  .catch(error => console.log('error:', error))

Mas aí fiquei na dúvida...

Eu preciso checar res.ok antes de retornar a promise do res.json().

Nesse caso, colocaria um try catch dentro do map?

<button>Click-me</button>
const button = document.querySelector('button')

button.addEventListener('click', () => {
  const pokemonPromises = Array
    .from({ length: 3 })
    .map(async (_, index) => {
      try {
        const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${index + 1}`)

        if (!res.ok) {
          throw new Error('Falha na conexão. Não foi possível buscar os dados.')
        }

        return res.json()
      } catch (error) {
        console.log('error no catch do try/catch:', error)
        return error
      }
    })

  Promise
    .allSettled(pokemonPromises)
    .then(console.log)
    .catch(error => console.log('error:', error))
})

No exemplo acima, inseri um <button> na marcação HTML e, antes de clicar no botão, simulei offline network no devtools.

Ao clicar no botão, o callback do método catch foi executado 3x.

example

Pelo que entendi, como houve falha na conexão, o fetch lançou um erro, que foi pego no catch (do try catch), e o return inseriu o erro no array que o map estava gerando.

Por último, assim que o array de promises foi resolvido, ele foi exibido no console.

Minhas dúvidas são as seguintes...

Pq o if (!res.ok) não foi executado?

O método catch encadeado no then não foi executado pq não houve lançamento de erro por parte do then?

@fdaciuk

fdaciuk commented 2 years ago

Oi @pgstudies22 ! Vamos lá: seu exemplo está correto: ao clicar no botão, as 3 Promises serão criadas e, ao serem resolvidas, o resultado será exibido.

O if (!res.ok) não foi executado pq o fetch deu erro, já que você não tinha rede. O fetch só cai no catch se acontecer erro de rede, que foi o seu caso. Se tivesse rede, mas o request respondesse com erro, aí sim ele passaria naquele if :)

E o .catch nunca será executado no allSettled, pq esse método não quebra! Todas as Promises resolvidas com sucesso ou com falha só caem no then, e cada resultado recebe um status de fulfilled (se finalizou com sucesso) ou rejected (se finalizou com erro).

Ficou claro?

fdaciuk commented 2 years ago

Tem ainda um último detalhe: como você tratou o erro na primeira promise (com try/catch), todas as Promises irão estar com status fulfilled, já que o erro que estourar nunca vai chegar no allSettled, a menos que você remova o try/catch, ou dê throw em um erro customizado dentro do catch :)

pgstudies22 commented 2 years ago

Ahhh, acho que estou começando a entender professor =D

E o .catch nunca será executado no allSettled, pq esse método não quebra!

Que massa! Estou conhecendo esse método agora =)


E realmente, eu testei remover try/catch e na falha da rede, as promises contém status rejected e reason com a mensagem do erro.

button.addEventListener('click', () => {
  const pokemonPromises = Array
    .from({ length: 3 })
    .map(async (_, index) => {
      const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${index + 1}`)
      return res.json()
    })

  Promise
    .allSettled(pokemonPromises)
    .then(p => console.log('p:', p)) // [{ status: 'rejected', reason: '...' }, {…}, {…}]
})

Seria ótimo assim, um if a menos no código.

Mas, é meio que mandatório testar o !res.ok pra fazer algo caso haja rede mas o status HTTP não tenha vindo no range 200-299, né?

Será que há alguma forma mais interessante do que if para checar .ok?

Ao fazer o request com um endpoint que não existe, como é um caso em que baseado em uma condição um valor precisa ser retornado, usei ternário.

try {
  const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${index + 1}/sadasdas`)
  return res.ok ? res.json() : new Error('Não foi possível obter os dados.')
} catch (error) {
  console.log('error no catch do try/catch:', error)
  return error
}

Alguns possíveis problemas que vejo nessa abordagem (posso estar equivocado)...

  1. Não usei o throw;
  2. Se mais checagens tiverem que ser feitas no res.ok, o código "quebra" (entre aspas pq eu refatoraria para if ao invés de aninhar ternários).

😬

@fdaciuk

fdaciuk commented 2 years ago

Mas, é meio que mandatório testar o !res.ok pra fazer algo caso haja rede mas o status HTTP não tenha vindo no range 200-299, né?

Depende de como você vai tratar os erros. Você pode tratar e dar throw em um erro diferente, customizado, pra ficar mais fácil de tratar na hora de usar o allSettled :)


Será que há alguma forma mais interessante do que if para checar .ok?

Não, esse tem que ser assim mesmo :)


Alguns possíveis problemas que vejo nessa abordagem (posso estar equivocado)... 1) Não usei o throw; 2) Se mais checagens tiverem que ser feitas no res.ok, o código "quebra" (entre aspas pq eu refatoraria para if ao invés de aninhar ternários).

Exatamente! Eu faria assim:

if (!res.ok) {
  throw new Error('Não foi possível obter os dados.')
}

return res.json()
pgstudies22 commented 2 years ago

Show professor. Muito obrigado pelos esclarecimentos.

O suporte que vc dá aos alunos é fora da curva. Praticamente uma mentoria, hahah.

Se quiser fechar a issue, por mim tudo bem. Minhas dúvidas foram sanadas =)

@fdaciuk

fdaciuk commented 2 years ago

Massa @pgstudies22, que bom que tudo ficou claro! :D Qualquer dúvida, fique à vontade para perguntar :)