Skip to content

Instantly share code, notes, and snippets.

@eduardo-matos
Last active December 7, 2018 20:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eduardo-matos/59b863927d91c91c64b01a561b2ff8a8 to your computer and use it in GitHub Desktop.
Save eduardo-matos/59b863927d91c91c64b01a561b2ff8a8 to your computer and use it in GitHub Desktop.
Workshop de NodeJS + Promises

NodeJS

Node é single threaded, istoé, ele é incapaz de executar duas coisas ao mesmo tempo, com exceção de operaçes de IO.

Exemplos de operação não bloqueantes: Acesso à rede, acesso à arquivos etc.
Exemplos de açes bloqueantes: Loop num array, cálculos complexos etc.

Em node, o padrão é toda função assíncrona ter uma versão síncrona, por exemplo fs.readFile e fs.readFileSync.
As verses síncronas são úteis para aplicações que não dependem de concorrência, como scripts e programas de linha de comando por exemplo.
Via de regra usamos a versão assíncrona. Em uma API, por exemplo, qualquer operação bloqueante impede que novas requisiçes sejam processadas pelo servidor, por isso é importante sempre fazer chamadas não bloqueantes.

Exemplo de chamadas não-bloqueante e bloqueante:

var fs = require('fs');

// não-bloqueante
fs.readFile('filename', 'utf8', function(err, contents) {
  if (err) {
    console.log(err);
  } else {
    console.log(contents);
  }
});

// bloqueante!
try {
  const contents = fs.readFileSync('filename', 'utf8');
} catch (err) {
  console.log(err);
}

Assinatura de callbacks

Em Node existe um padrão para chamadas assíncronas. A função sempre recebe um callback como último parâmetro, e este callback tem como primeiro parâmetro o erro ocorrido. Caso nenhum erro tenha ocorrido, o valor desse parâmetro é null. Vide fs.readFile.

Como isso é um padrão, a biblioteca padrão do Node tem uma função que converte essa chamada de callback em promise:

const util = require('util')

const readFilePromisified = util.promisify(fs.readFile);

readFilePromisified('filename', 'utf8')
  .then(contents => console.log(contents))
  .catch(err => console.log(err));

Callback hell

O uso exagerado de callbacks dificulta a leitura do código.

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename);
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err);
        } else {
          console.log(filename + ' : ' + values);
          aspect = (values.width / values.height);
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect);
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err);
            });
          }.bind(this));
        }
      });
    });
  }
});

Promise

Wikipedia:

In computer science, future, promise, delay, and deferred refer to constructs used for synchronizing program execution in some concurrent programming languages. They describe an object that acts as a proxy for a result that is initially unknown, usually because the computation of its value is yet incomplete.

Promises têm o mesmo papel de callbacks, isto é, elas também executam uma função quando uma operação termina. A diferença é que no caso de callbacks, a mesma função será executada em caso de sucesso ou falha, já nas promises é possível ter uma função pra cada resultado.

function successIfOdd(value) {
  return new Promise((resolve, reject) => {
    if (value % 2 == 0) {
      resolve(value);
    } else {
      reject(value);
    }
  });
}

successIfOdd(1).then(value => console.log(value)); // 1
successIfOdd(2).catch(value => console.log(value)); // 2

Estados

Promises podem estar em um de três estados: pending, rejected ou fulfilled.

  1. Pending: A promise não foi resolvida nem rejeitada.
  2. Rejected: A promise foi rejeitada.
  3. Fulfilled: A promise foi resolvida.

Fluxo

O callback do método then só é chamado quando uma promise é resolvida (resolve).
O callback do método catch é chamado quando uma promise é rejeitada (reject), ou quando ocorre algum erro não capturado dentro da promise.

function foo() {
  return new Promise((resolve, reject) => resolve()); // cairá no `then`
  return new Promise((resolve, reject) => reject()); // cairá no `catch`
  return new Promise((resolve, reject) => { throw new Error('Oops'); }); // cairá no `catch`
}

Uma vez que um erro foi capturado no catch, você pode encadear outros then.

function foo() {
  return new Promise((resolve, reject) => {
    reject('Oops');
  });
}

foo()
  .catch(() => console.log('Deu m*'))
  .then(() => console.log('Isso será printado =)'))
  .then(() => { throw new Error('😱'); })
  .catch(err => console.log(err))
  .then(() => console.log('Como o erro foi capturado, irei executar! 😁'));

Encadeamento de transformações

É possível transformar dados no encadeamento de then:

function someApiCallResult() {
  return new Promise((resolve, reject) => {
    resolve('{"foo":1}');
  });
}

someApiCallResult()
  .then(res => JSON.parse(res))
  .then(json => ({ bar: 2, ...json }))
  .then(console.log); // {bar: 2, foo: 1}

Resolvendo/Rejeitando imediatamente

Em alguns casos é útil resolver ou rejeitar uma promise imediatamente (em testes automatizados por exemplo):

function foo() {
  return new Promise((resolve, reject) => {
    resolve('value'); // ou reject('value');
  });
}

// ou 

function foo() {
  return Promise.resolve('value'); // ou Promise.reject('value')
}

Execuções em paralelo

Eventualmente é útil executar diversas promises em "paralelo" e aguardar todas finalizarem para proseguie com a lógica do código. Um caso típico é quando se deseja fazer vários insert no banco de dados. Pra isso ns utilizados Promise.all.

function foo() {
  return new Promise(resolve => {
    setTimeout(() => resolve({ foo: 1 }), 200);
  });
}

function bar() {
  return new Promise(resolve => {
    setTimeout(() => resolve({ bar: 2 }), 400);
  });
}

Promise.all([
  foo(),
  bar(),
]).then(([ fooResult, barResult ]) => {
  console.log(fooResult);
  console.log(barResult);
});

Assim como em qualquer promise, se uma das promises forem rejeitadas, somente o catch ser chamado. A primeira a ser rejeitada enviará seu erro pro catch.

function foo() {
  return Promise.resolve('Yay');
}

function bar() {
  return Promise.reject('Eita');
}

Promise.all([
  foo(),
  bar(),
]).then(() => console.log('Nunca serei executado =('))
  .catch(err => console.log(err)); // Eita

Pegando o resultado da primeira que resolver

Em outros casos somente estamos interessados em saber quando a primeira promise terminar. Nesse caso usamos Promise.race:

function foo() {
  return new Promise(resolve => {
    setTimeout(() => resolve('Poxa'), 150);
  });
}

function bar() {
  return new Promise(resolve => {
    setTimeout(() => resolve('Vida'), 100);
  });
}

Promise.race([
  foo(),
  bar(),
]).then(console.log); // 'Vida'

Aninhamento

Quando uma promise retorna outra promise, o resultado final é o resolvido pela última promise.

function foo() {
  return Promise.resolve({ foo: 1 });
}

function bar() {
  return Promise.resolve({ bar: 2 });
}

function baz() {
  return foo().then(fooResult => {
    return bar().then(barResult => {
      return { baz: 3, ...barResult, ...fooResult };
    }); 
  });
}

baz().then(console.log); // { baz: 3, bar: 2, foo: 1 }

Async/Await

Async/await SEMPRE é usado em conjunto com promises.

Regras:

  1. Se uma função faz uso de await, ela precisa ter a keyword async antes de sua definição.
  2. Se uma função retorna uma promise, você pode dar await nela.
function foo() {
  return Promise.resolve({ foo: 1 });
}

async function bar() {
  const result = await foo();
  console.log(result);
}

bar(); // { foo: 1 }

Lidando com erros

Se a promise for rejeitada, ela levantará um erro se você estiver usando await, então é necessário encapsular o trecho de código num bloco try/catch.

function foo() {
  return Promise.reject({ some: 'error' });
}

async function bar() {
  try {
    await foo();
  } catch (e) {
    console.log(e);
  }
}

bar(); // { some: 'error' }

Await em paralelo

Para executar várias promises em "paralelo" usando async/await, ainda é possvel usar Promise.all, visto que essa função retorna uma promise.

function foo() {
  return Promise.resolve({ poxa: 'vida' });
}

function bar() {
  return Promise.resolve({ hein: 'wow' });
}

async function baz() {
  const [fooResult, barResult] = await Promise.all([foo(), bar()]);

  console.log(fooResult); // { poxa: 'vida' }
  console.log(barResult); // { hein: 'wow' }
}

baz();

Outro

Curso básico de promise: https://br.udacity.com/course/javascript-promises--ud898

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment