Thursday, April 18, 2024

Working with Services in JavaScript

JavaScript is a multi-paradigm language. You can “emulate” various programming techniques with it, and this is incredible because we can decide the best paradigm for solving the problems of our projects.

But if, on the one hand, this gives a great power to the language, it can also leave beginners confused. It is very common to choose the wrong approach for a problem.

Thinking about it many developers create their own solutions, some thinking of helping others in enforcing standards.

Encapsulating Logics

Everything in JavaScript is an object (except undefined), so services are also objects. This way, mastering how we create and work with objects is much simpler.

Scope and Closures

JavaScript has a lexical scope. Among other things, this allows you to create closures. In a nutshell, you create a “controlled environment” where there are functions/variables that can only be accessed in that scope, creating the closure.

const initPage = (root) => {
    const $root = $(root);
    const $menu = $root.find('.menu');
    const $profile = $menu.find('.profile');

    const initProfile = () => {
        $.get('/me')
            .then(response => {
                $profile.text(response.username);
            });
        //  ...
    };

    const showProfileModal = e => {
        // ...
    };

    $profile.on('click', e => showProfileModal(e));

    initProfile();
};

This is a simple example, but it illustrates just how well we created closures. Variables declared inside initPage only exist in that scope.

In the same example we can refactor this code in an Immediately-Invoked Function Expression (IIFE).

((root) => {
  const $root = $(root);
  const $menu = $root.find('.menu');
  const $profile = $menu.find('.profile');

  const initProfile = () => {
    $.get('/me')
     .then(response => {
       $profile.text(response.username);
     });
    //  ...
  };

  const showProfileModal = e => {
    // ...
  };

  $profile.on('click', e => showProfileModal(e));

  initProfile();
})('body');

In this code, we declare a function and execute it immediately, passing an argument. This is extremely useful when we want to perform processing of information that will only serve to create a variable.

const timezones = (() => {
  const zones = [];
  const min = -12;
  const max = 13;
  let simbol;

  for (let i = min; i <= max; i++) {
    simbol = (i < 0) ? '' : '+';
    zones.push(`GMT${simbol}${i}`);
  }

  return zones;
})();

As you may already have realized, you can expose data from a closure as in the previous example. The “zones” variable is returned, so the “timezones” variable now has the closure result as its value.

In this example, the closure does not use external data (parent scope), but given the JavaScript nature… this is perfectly possible.

This is useful to not pollute the main scope with irrelevant information.

const makeCounter = (start = 0) => {
  let current = start;

  const add = (value = 1) => current += value;
  const remove = (value = 1) => add(value * -1);
  const get = () => current;

  return { add, remove, get };
};

const counter = makeCounter(10);

counter.add() // 11
counter.add() // 12
counter.add(8) // 20
counter.remove(10) // 10

In this example, we are combing closures with factory. With this, we can create several counters, and work as best as we can with them.

If you understand how the counter example works, congratulations you already know how to create services with JavaScript. That’s right, this counter is a service. In fact it is a “factory,” but with small adjustments it becomes a service that is easy to reuse.

To reinforce, I will leave another example of use of services:

import Http from './http.js';
import UsersService from './modules/users/service.js';

Http.setToken('XPTO'); // Define o token de autentificação

// Cattega a primeira página de usuários
// Exibe um alerta com o nome do primeiro usuário retornado pelo serviço

UsersService
  .getAll({ page: 1 })
  .then(result => result.data)
  .then(data => data[0])
  .then(first => {
    alert(first.name);
  });

The Http service is also used by the users service, so it is possible to set the authentication token before actually using the services because they will share the same status/service.

Another interesting feature is that these services are not directly linked to any context. This means that no matter what environment you are in or what framework you are using, the services are agnostic. They can be used in Node.JS, Vue.JS, React.JS, etc.

This is one of the principles of JavaScript Polymorphisms … however this is a subject for another day.

About the Author

Diogo Souza works as a Java Developer at PagSeguro and has worked for companies such as Indra Company, Atlantic Institute and Ebix LA. He is also an Android trainer, speaker at events on Java and mobile world.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Popular Articles

Featured