Tratando Async Code em JS

@lucasktrindade

Quem sou eu?

O Javascript é síncrono... 

seu código vai ser executado após o hoisting.

Ex. de código síncrono

console.log('1')

console.log('2')

console.log('3')

O resultado vai ser: "1 2 3"

Ex. de código assíncrono

console.log('1')

setTimeout(function twoSec() {
  console.log('2')
}, 2000)

console.log('3')

O resultado vai ser: "1 3 2"

Temos um problema...

desejamos pegar os dados de um request para exibir as wallets e logo após os cartões, porém isto acontece assíncronamente.

function request (method, url, token) {
    const xhr = new XMLHttpRequest();
    let response;
    xhr.open(method, url, true);
    xhr.setRequestHeader ("Authorization", token);
    xhr.onreadystatechange = function () {        
        if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {        
            response = JSON.parse(xhr.response);
            console.log(response);
            // [{…}, {…}]
        } else {
            // Código para tratar erro
        }
    }
    xhr.send()

    return response
};

const wallets = request('GET', url, token);
console.log(wallets)
// undefined

Ex. de request

Como lidar com o código assíncrono no JS?

Temos algumas formas:

  • callbacks

  • promises

  • async/await
  • observables

Callbacks

No JS você pode salvar a referência de uma função em uma variável. E você pode usar isso como argumento de outra função para executar depois.

Simples callback

nome(sobrenome);

function sobrenome() {
    console.log('Trindade');
}

function nome(callback) {
    console.log('Lucas');
    callback();
}





Callbacks para requests...

callbacks para resolver nosso problema anterior

function request (method, url, token, callback) {
    xhr.open(method, url, true);
    xhr.setRequestHeader ("Authorization", token);
    xhr.onreadystatechange = function () {
        if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            callback(null, JSON.parse(xhr.response));            
        } else {
            callback(xhr.status, xhr.response)
        }
    }
    xhr.send()
};
request('GET', url, token, function printWallet(err, wallets) {
    if(err) throw err

    wallets.forEach(function(element) {
        var wallet = document.createElement("li"); 
        var content = document.createTextNode(JSON.stringify(element));
        wallet.appendChild(content);
        var domWallets = document.getElementById('wallets');
        domWallets.appendChild(wallet);
    }, this);

});
request('GET', url, token, function printWallet(err, wallets) {
    if(err) throw err;
    wallets.forEach(function(element) {
        let url = `https://best-credit-card.herokuapp.com/v1/cards/wallets/${element.id}`;
        request('GET', url, token, function printCards(err,cards) {
            if(err) throw err;
            console.log(cards)
        })
    }, this);
});

Porém...

nem tudo é perfeito e temos alguns problemas

Callback Hell

request('GET', url, function(err, result) {
    if(err) throw err;
        request('GET', url + result, function(err, result) {
            if(err) throw err;
                request('GET', url, function(err, result) {
                    if(err) throw err;
                        request('GET', url + result, function(err, result) {
                            if(err) throw err;
                            console.log(result)
                        })
                });
        })
});

Como melhorar isso?

como podemos deixar o código legível e manutenível.

Promises

é um objeto usado para processamento assíncrono. Representa um valor que pode estar disponível agora, no futuro ou nunca.

const testePromise = new Promise(function(resolve, reject) {
  
  if (ok) {
    resolve('tudo certo')
  } else {
    reject('erro')
  }
})


testePromise
  .then(function(response) {
    console.log(response)
    return response
  })
  .catch(function(err) {
    console.error(err)
  })

Levando para nosso contexto....

aplicando promises em um request

function requestPromise (method, url, token) {
    return new Promise (function (resolve, reject) {
        const xhr = new XMLHttpRequest();        
        xhr.onload = function () {
            if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                resolve(xhr.response)         
            } else {
                reject(xhr.statusText)
            }
        }
        xhr.ontimeout = function () {
            reject('timeout')
          }
        xhr.open(method, url, true);        
        xhr.setRequestHeader ("Authorization", token);        
        xhr.send()
    })
}
requestPromise('GET', url, token)
.then((response) => {
    const wallets = JSON.parse(response)
    return Promise.all(wallets.map(function(wallet){
      console.log(wallet);
      return requestPromise('GET', `https://best-credit-card.herokuapp.com/v1/cards/wallets/${wallet.id}`, token)
    }))
})
.then(cards => {
  console.log(cards);
})
.catch(err => {
    console.log(err)
});

Com promises:

  • melhoramos a leitura

  • melhoramos o tratamento de erros
  • e também problemas do código async

É possível melhorar?

function handleWallets (response) {
    return JSON.parse(response)
}

function handleWalletsReq(wallets) {
    return Promise.all(wallets.map(function(wallet){
      return requestPromise('GET', 
      `https://best-credit-card.herokuapp.com/v1/cards/wallets/${wallet.id}`, token)
    }))  
}

function handleCards (cards) {
    console.log('Cards de cada wallet', cards);
}

function handleError (error) {
    console.error('Deu ruim', error)
}
requestPromise('GET', url, token)
.then(handleWallets)
.then(handleWalletsReq)
.then(handleCards)
.catch(handleError);

Async/Await

permite a escrita do código baseado em promise como se fosse síncrono mas sem bloquear a thread.

Ex. de request

function request (method, url, token) {
    const xhr = new XMLHttpRequest();
    let response;
    xhr.open(method, url, true);
    xhr.setRequestHeader ("Authorization", token);
    xhr.onreadystatechange = function () {        
        if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {        
            response = JSON.parse(xhr.response);
            console.log(response);
            // [{…}, {…}]
        } else {
            // Código para tratar erro
        }
    }
    xhr.send()

    return response
};

const wallets = request('GET', url, token);
console.log(wallets)
// undefined
async function myFirstAsyncFunction() {
  try {
    const fulfilledValue = await promise;
  }
  catch (rejectedValue) {
    // …
  }
}

Async/Await

function requestAsync (method, url, token) {
    return new Promise (function (resolve, reject) {
        const xhr = new XMLHttpRequest();
        xhr.onload = function () {
            if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                resolve(xhr.response)
            } else {
                reject(xhr.statusText)
            }
        }
        xhr.ontimeout = function () {
            reject('timeout')
          }
        xhr.open(method, url, true);
        xhr.setRequestHeader ("Authorization", token);
        xhr.send()
    })
}

Aplicando ao nosso contexto...

async function listWallets() {
    const url = 'https://best-credit-card.herokuapp.com/v1/me/wallets'
    const token = "TOKEN_ACCESS";
    const wallets = await requestAsync('GET', url, token);  
         
    console.log(wallets)
    /* [{"id":4,"current_limit":0,"maximum_limit":0,"avaliable_limit":0,"person_id":1},
     * {"id":1,"current_limit":1530,"maximum_limit":3200,"avaliable_limit":0,"person_id":1}]
     */

}

async function listWallets() {
    const url = 'https://best-credit-card.herokuapp.com/v1/me/wallets'
    const token = "TOKEN_ACCESS";
    const wallets = await requestAsync('GET', url, token);  
         
   wallets.map(async (wallet) => {
        const cards = await requestAsync('GET', 
                            `https://best-creditcard.herokuapp.com/v1/cards/wallets/${wallet.id}`, token)

        handleCards(wallet, cards);
    })
}

Cuidado...

tente ao máximo otimizar seu código para funcionar em paralelo!

async function serie() {
  await wait(500);
  await wait(500);
  return "caubanga";
}

Em quanto tempo vai levar essa execução?

async function paralelo() {
  const wait1 = wait(500);
  const wait2 = wait(500);
  await wait1;
  await wait2;
  return "caubanga";
}

E esta execução?

Com Async/Await:

  • você consegue ter um código legível

  • escrever async como se fosse sync

  • e é possível usar junto com promises

E o suporte?

Observables

com observables você trabalha com stream de dados, a origem dos dados pode emitir vários valores. E você ainda tem operadores para manipular e transformar seus dados.

RxJS

const button = document.querySelector('button');
const observable = Rx.Observable.fromEvent(button, 'click');
observable.subscribe(
  (event) => { console.log(event.target) }
);

Observables

button.subscribe({
  next: (event) => console.log(`You just clicked ${event.target}!`),
  error: (err) => console.log(`Oops... ${err}`),
  complete: () => console.log(`Complete!`)
});

Observer

const input$ = Rx.Observable.fromEvent(node, 'input')
  .map(event => event.target.value)
  .filter(value => value.length >= 2)
  .subscribe(value => {
    console.log(value)
  });

Operadores

Temos várias formas...

de tratar um chamada assíncrona.

Callbacks

se você não tiver outra opção e precise lidar apenas com uma operação async.

Promises

é uma ótima forma para tratar suas operações async de forma estruturada e previsível.

Async/Await

você ganha o poder de ter seu código escrito de maneira síncrona e tratar o encadeamento das promises de maneira mais simples.

Observables

você pode usar em situações que você lide com um stream de dados e você tem o suporte dos operadores também.

Escolha aquilo que te atenda!

pense no escopo da sua webapp, refatore caso seja necessário!

Referências

OBRIGADO!

@lucasktrindade

Async JS

By Lucas Trindade

Async JS

  • 383