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
Github com exemplos: https://github.com/Lucasktrindade/async-code
OBRIGADO!
@lucasktrindade
Async JS
By Lucas Trindade
Async JS
- 383