Pular para o conteúdo principal

Fui ensinar Promise e acabei construindo um agregador de jogos

Antes que crie alguma expectativa, aqui está o link do "Fliperama".
Há uns dias atrás, Eu estava conversando com um colega de trabalho, e discutindo sobre como ele poderia melhorar o código que estava escrevendo. Para começo de conversa, passei algumas noções de Clean Code, que aprendi com o livro de Robert C. Martin, Clean Code, e com a experiência de pôr esses conhecimentos em prática no dia-a-dia. E durante o processo falei da importância de um código não ser repetitivo, conciso, e que sua escrita de fato entregasse valor durante a leitura, sem que fosse necessário minuciosamente ler os detalhes de implementação de uma função para que se soubesse o que ela realmente faz.
Foi aí que falei como o uso de Promise em Javascript podem tornar um código mais legível e com maior mantenabilidade. Em essência, Promise é um objeto usado para processamento assíncrono e seu retorno sempre será um callback que precisa ser resolvido.
Para que Eu não me alongasse muito, resolvi desenvolver uma página simples, que agregaria links de jogos que são emulados no browser usando um iframe para embutir o jogo na nossa página. Pra isso, era necessário elaborar uma galeria que apresentasse os jogos, com um botão call-to-action que pegasse a URL do item da galeria e injetasse no iframe para carregar o jogo.
Dada essas premissas, o resultado do código Javascript foi esse:


window.onload = () => {
  const games = [
    {
      img: "crash-team-racing.png",
      name: "Crash Team Racing",
      url: "https://www.retrogames.cc/embed/41687-crash-team-racing.html",
    },
    {
      img: "hercules.jpg",
      name: "Hercules",
      url: "https://www.retrogames.cc/embed/41734-disney-s-hercules.html",
    }
  ];
  const plugins = [
    "https://cdnjs.cloudflare.com/ajax/libs/flickity/2.2.1/flickity.pkgd.min.js",
  ];

  App.createGallery(games)
    .then(function () {
      return App.loadPlugins(plugins);
    })
    .then(function () {
      App.bindPlayButtonEvent();
    });
};

const App = {
  createGallery: function (games) {
    const $galleryElement = document.getElementById("gallery"),
      assetsPath = "assets/img/";
    let galleryValue = "",
      div = document.createElement("div");

    games.forEach(function (game) {
      galleryValue += App.getGalleryItemTemplate()
        .replace("{url}", game.url)
        .replace("{name}", game.name)
        .replace("{img}", assetsPath + game.img);
    });

    div.innerHTML = galleryValue;
    while (div.children.length > 0) {
      $galleryElement.appendChild(div.children[0]);
    }

    return new Promise((resolve) => resolve());
  },
  getGalleryItemTemplate: function () {
    return `<div class="gallery-cell" style="background-image: url({img});background-repeat: no-repeat;background-position: center;background-size: cover;"><span class="game-name">{name}</span><a class="play-game" href="{url}">Jogar</a></div>`;
  },
  bindPlayButtonEvent: function () {
    const $playButtons = document.querySelectorAll(".play-game");
    const $screen = document.querySelector("iframe");
    const $fakeScreen = document.querySelector("#screen .fake-screen");
    $playButtons.forEach(($play) => {
      $play.addEventListener("click", (event) => {
        event.preventDefault();
        let game = event.target;
        $screen.setAttribute("src", game.getAttribute("href"));
        window.location.hash = "#screen";
        if ($screen.classList.contains("hidden")) {
          $screen.classList.remove("hidden");
          $fakeScreen.classList.add("hidden");
        }
      });
    });
  },
  loadPlugins: function (plugins) {
    plugins.forEach(function (plugin) {
      let newPlugin = document.createElement("script");
      newPlugin.type = "text/javascript";
      newPlugin.async = true;
      newPlugin.src = plugin;
      (
        document.querySelectorAll("head")[0] ||
        document.querySelectorAll("body")[0]
      ).appendChild(newPlugin);
    });
    return new Promise((resolve) => resolve());
  },
};


Veja que as funções que realizam a criação de elementos e binding de eventos estão definidas em uma constante App. As que são acessadas externamente para cumprir a premissa são vistas no load da página: createGallery; loadPlugins; e bindPlayButtonEvent.
Elas acontecem de forma encadeada e o resultado da execução de uma é o input da seguinte na cadeia de Promises. É incrível como isso fica tão fácil de compreender. Perceba que se Eu isolasse essa constante App que possui os detalhes de implementação, em outro arquivo, Eu não precisaria abrir o arquivo pra perceber que um erro nos eventos de clique do botão está associado à função bindPlayButtonEvent.
Enfim, esse post não foi feito com a intenção de explicar o que são Promises, mas de dar um vislumbre de sua capacidade. É claro que com as novas especificações da linguagem javascript, temos a opção de usar async/await quando queremos trabalhar com requisições assíncronas, mas isso é história pra outro post.

Comentários

Postagens mais visitadas deste blog

Como serializar todos os dados de um formulário em um objeto JSON usando jQuery? #fastTip

Um dos pilares de um código bem escrito é: DRY (Dont Repeat Yourself - não se repita). Acontece que muitas vezes nos deparamos com situações que por conta da necessidade de entrega expressa (famoso código miojo - feito em 2 minutos), acabamos deixando de aplicar boas práticas. O que difere um amador de um profissional é a sua capacidade de avaliar o impacto que um código mal escrito causa e identificar os caminhos pra resolver esses impasses. Enfim, vamos ao que interessa: O Problema Desejo criar um objeto JSON à partir de um formulário, para ser enviado em uma requisição assíncrona. Atualmente, o objeto é criado assim: var data = { name: $('input[name="name"]').val(), email: $('input[name="email"]').val(), }; Olhando pra esse código, vemos diversos problemas: Se mudar o name do input, precisará ir lá no código para alterar a atribuição. Se houver um formulário com 20 campos, terá que fazer 20 atribuições, e assim por diante. A Soluçã

Prefira exceções à códigos de erros #FastTip #CleanCode

Gostaria de compartilhar com vocês uma técnica que aprendi a me acostumar ao longo dos anos, mas que conheci pelo livro do Robert C. Martin, o Clean Code . De forma bem sintetizada, o autor relata que uma boa prática é retornar exceções, ao invés de códigos de erro, pois isso evita a escrita de códigos de verificação, e mantém ele enxuto, mais confiável, sem aberturas para bugs relacionados à erros não previstos. Esse conteúdo você encontrará no capítulo 3 (página 46 - 47) e no capítulo 7 (página 103 - 112), do livro supracitado. Aí como exemplo, desenvolvi um pequeno código Javascript que pode servir de referência para os demais, por ser simples de entender. // ----------------------------- // Criei uma classe genérica que permite acesso ao objeto // Error, padrão do JS, e escrevo a stack trace nomeada // à partir de um Error genérico class GenericError extends Error { constructor(message) { super(message); this.name = this.constructor.name; Error.captureStackTrace(t

Executar uma tarefa em background no Android usando React Native

Pegue um café e se ajeite na cadeira, pois vou contar uma história um pouco longa a respeito de como executar serviços em segundo plano com React-Native e imagino que vai servir mais como um norte na hora de desenvolver esse tipo de funcionalidade. Primeiro, pesquisei na documentação oficial do React, e lá é indicado um recurso chamado Headless JS, que permite criar uma ponte entre o JS e o código nativo, e executar tarefas em background. Em seguida, fiz uma pesquisa sobre como funcionam as tarefas em segundo plano no Android para entender quais são as possibilidades e prós e contras de cada abordagem. Sobre as abordagens, temos: # WorkManager A API WorkManager facilita a programação de tarefas adiáveis e assíncronas que precisam ser executadas mesmo se o app fechar ou o dispositivo reiniciar. Principais recursos: Compatível com versões anteriores até a API 14 Usa o JobScheduler em dispositivos com a API 23 ou posteriores Usa uma combinação de BroadcastReceiver + AlarmManage