Programação assíncrona

Quando fazemos uma ligação telefônica para uma pessoa para passar uma mensagem, dependemos de outra ação que é a da pessoa atender a chamada. Vamos tentar representar isso em código utilizando a linguagem JavaScript:

function ligacao() {
console…


This content originally appeared on DEV Community and was authored by thaisandre

Quando fazemos uma ligação telefônica para uma pessoa para passar uma mensagem, dependemos de outra ação que é a da pessoa atender a chamada. Vamos tentar representar isso em código utilizando a linguagem JavaScript:

function ligacao() {
    console.log("eu faço a chamada");
    console.log("a pessoa atende e diz alô");
    console.log("eu digo alguma informação"); 
}

ligacao();

A saída será:

eu faço a chamada
a pessoa atende e diz alô
eu digo alguma informação

Callbacks

Na realidade, a pessoa não atende a mensagem imediatamente, ela pode demorar alguns segundos para atender. Podemos representar essa "demora" através da função setTimeout() que executa uma função após determinado espaço de tempo. Ela recebe dois argumentos - o primeiro é a função que representa a ação a ser executada e o segundo o valor em milisegundos representando o tempo mínimo de espera para que ela seja executada:

setTimeout(() => {
    console.log("a pessoa atende e diz alô")
}, 3000);

Como resultado, após 3 segundos, temos:

a pessoa atende e diz alô

Agora vamos utilizar este recurso no nosso exemplo:

function ligacao() {
    console.log("eu faço a chamada");
    setTimeout(() => {
        console.log("a pessoa atende e diz alô")
    }, 3000);
    console.log("eu digo alguma informação"); 
}

saída:

eu faço a chamada
eu digo alguma informação
a pessoa atende e diz alô

Note que nosso programa apresenta alguns problemas. A pessoa que fez a chamada (no caso, eu) acaba dizendo alguma coisa antes da outra pessoa antender. Ou seja, a execução não aconteceu de maneira síncrona, mantendo a ordenação esperada. O conteúdo dentro de setTimeout() não foi executado após a primeira chamada de console.log().

Isso acontece porque o Javascript é single-thread. O que quer dizer, a grosso modo, que possui uma stack principal de execução do programa e executa um comando por vez, do início ao fim, sem interrupções. No momento em que cada operação é processada, nada mais pode acontecer.

Acabamos de ver que seu funcionamento é diferente quando o programa encontra a função setTimeout(). O método setTimeout pertence ao módulo timers que contém funções que executam algum código após um determinado período de tempo. Não é necessário importar este módulo no node.js já que todos estes métodos estão disponíveis globalmente para simular o JavaScript Runtime Environment dos navegadores.

A chamada da função que passamos como primeiro argumento para o setTimeout() é enviada para outro contexto, chamado WEBApi que define um timer com o valor que passamos como segundo argumento (3000) e arguarda este tempo para colocar a chamada da função na stack principal para ser executada - ocorre um agendamento desta execução. Porém, este agendamento só é concretizado após a stack principal ser limpa, ou seja, após todo código síncrono ser executado. Por este motivo, a terceira e última chamada de console.log é chamada antes da segunda.

A função que passamos como primeiro argumento para o método setTimeout() é chamada de função callback. Uma função callback é toda função passada como argumento para outra função que de fato vai executá-la. Esta execução pode ser imediata, ou seja, executada de maneira síncrona. No entando, callbacks são normalmente utilizados para continuar a execução de um código em outro momento na linha do tempo, ou seja, de maneira assíncrona. Isso é bastante útil quando temos eventos demorados e não queremos travar o restante do programa.

Nosso código ainda tem problemas. A pessoa que faz a ligação quer apenas dizer alguma coisa após a outra pessoa atender a chamada. Podemos refatorar o código da seguinte maneira:

function fazChamada(){
    console.log("eu faço a chamada");
}

function pessoaAtende() {
    setTimeout(() => {
        console.log("a pessoa atende e diz alô")
    }, 3000);
}

function euDigoAlgo() {
    setTimeout(() => {
        console.log("eu digo alguma informação");
    }, 5000);
}

function ligacao() {
    fazChamada();
    pessoaAtende();
    euDigoAlgo();
}

ligacao();

Podemos definir um tempo de espera maior para dizer algo na chamada, mas ainda assim não sabemos ao certo o quanto a pessoa vai demorar para atender. Se ela atender imediatamente, vai demorar para receber a mensagem e desligar a chamada sem que isso aconteça. Além de ser bastante ruim e trabalhoso ficar configurando os tempos de cada execução, o código fica muito grande e confuso com muitas condicionais.

Promises

Para nossa sorte, o Javascript possui um recurso chamado Promise que representa, como seu nome sugere, uma promessa de algo que será executado futuramente. Como a execução que esperamos pode falhar, este recurso também ajuda muito nos tratamentos de erros.

Segundo o Wikipédia, um Promise atua como representante de um resultado que é, inicialmente, desconhecido devido a sua computação não estar completa no momento de sua chamada. Vamos contruir um objeto Promise para entender como ele funciona:

var p = new Promise();
console.log(p);

Isso vai gerar um TypeError com a mensagem "TypeError: Promise resolver is not a function". Um objeto Promise precisa receber uma função para resolver um valor. Ou seja, precisamos passar uma função callback para executar algo:

var p = new Promise(() => console.log(5));

Este código imprime o valor 5. Agora vamos imprimir o próprio objeto Promise:

var p = new Promise(() => console.log(5));
console.log(p);

Saída:

5
Promise { <pending> }

Note que executou o callback, mas seu estado está pendente. Toda vez que criamos um objeto Promise, seu estado inicial é pendente já que representa a promessa de algo que será resolvido no futuro. Neste caso, como o callback será executado de maneira síncrona, vai imprimir o resultado de sua execução. E, portanto, não é útil neste caso específico.

Pode acontecer do callback executar o processamento de um valor que será necessário no futuro. Para que este valor esteja disponível, será preciso que a promessa seja resolvida através da função anônima resolve() que cria uma nova promessa com o valor realizado. Exemplo:

var p = new Promise((resolve) => {
    resolve(5);
});
console.log(p);

Saída:

Promise { 5 }

Agora a promessa não está mais pendente, ela foi resolvida e embrulha o valor 5. Isso quer dizer que tudo deu certo. Porém, ainda é uma promessa. Para imprimir o valor, precisamos utilizar o método then() que anexa callbacks para a resolução:

var p = new Promise((resolve) => {
    resolve(5);
});
p.then(value => console.log(value));

Mas um erro pode acontecer quando a promessa tentar resolver um valor:

var p = new Promise((resolve) => {
    try {
        throw new Error("algo de errado ocorreu");
        resolve(5);
    } catch(err) {
        return err;
    }
});
console.log(p);
p.then(v => console.log(v))

A promessa está pendente, mas nada foi executado ao chamarmos then(v => console.log(v)). Isso acontece já que ela não foi resolvida por causa de um erro. Para sabermos qual erro ocorreu, precisamos passar outro callback que será responsável por tratar falhas quando a promessa de um resultado for rejeitada, chamado reject().

var p = new Promise((resolve, reject) => {
    try {
        throw new Error("algo de errado ocorreu");
        resolve(5);
    } catch(err) {
        reject(err);
    }
});

E após a chamada de then(), que só será executado em caso de sucesso, chamamos o catch() que será chamado em caso de erro:

var p = new Promise((resolve, reject) => {
    try {
        throw new Error("algo de errado ocorreu");
        resolve(5);
    } catch(err) {
        reject(err);
    }
});
// console.log(p);
p.then(v => console.log(v)).catch(err => console.log(err.message));

O estado da promessa será rejected e vai imprimir a mensagem de erro na execução do catch().

Promises são bastante úteis para chamadas assíncronas, quando precisamos saber sobre os estados de execuções futuras e tratar melhor as partes do código que dependam dessa execução.

Agora, vamos voltar ao nosso exemplo. Podemos utilizar Promises para melhorar o código e fazer com que a pessoa que fez a chamada diga algo após a outra pessoa atender a chamada:

function fazChamada(){
    console.log("eu faço a chamada");
}

function depoisDe(delay) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            let atendeu = Math.random() > 0.5;
            if(atendeu) {
                resolve("alô");
            } else {
                reject(new Error("a pessoa nao atendeu")); 
            }
        }, delay);

    });
}

function ligacao() {
    fazChamada();
    depoisDe(3000)
        .then((msg) => console.log(`a pessoa atende e diz ${msg}`))
        .then(() => console.log("eu digo alguma informação"))
        .catch(err => console.log(err.message));
}

ligacao();

Async/Await

Nosso código funciona e conseguimos representar uma chamada telefônica mais próxima da realidade. Porém, o código da função ligacao() possui uma chamada encadeada de várias promessas - e poderia ser muito mais complexo do que isso, como muitas chamadas encadeadas de then(). Dependendo da complexidade dessas chamadas, pode ser um código difícil de ler e entender. Um código síncrono é, na maioria dos casos, mais fácil de ler e entender.

Na especificação ES2017 foram introduzidas duas novas expressões - async e await - que deixam o trabalho com Promises mais confortável para o desenvolvedor. A expressão async é utilizada quando queremos criar funções assíncronas. Quando posicionada antes da declaração de uma função, quer dizer que essa função retorna um objeto do tipo Promise:

async function retornaUm() {
    return 1;
}
console.log(retornaUm());
retornaUm().then(console.log);

Que vai gerar a saída:

Promise { 1 }
1

Portanto, ao utilizar a expressão async em uma função, seu retorno é embrulhado em um objeto Promise. Agora que entendemos como funciona o async vamos ver como o await funciona.

O uso do await somente é permitido em escopo de um função async - deste modo, a palavra-chave async além de embrulhar seu retorno em uma promessa, permite o uso do await. A palavra-chave await faz com que o JavaScript espere até que uma promessa seja resolvida (ou rejeitada) e retorne seu resultado.

async function retornaUm() {
    return 1;
}

async function retornaDois() {
    var num = await retornaUm();
    return num + 1;
}

retornaDois().then(console.log)

A função retornaDois() faz uma pausa na primeira linha e segue sua execução quando a promessa retonraUm() é resolvida. Portanto, espera a promessa ser finalizada. O mesmo acontece quando o valor é rejeitado:

async function funcao() {
    await Promise.reject(new Error("um erro ocorreu"));
}

funcao().catch(err => console.log(err.message));

E é similar a:

async function funcao() {
    await new Error("um erro ocorreu");
}

funcao().catch(err => console.log(err.message));

Como lança um erro, podemos fazer um tratamento com o bloco try/catch:

async function funcao() {
    try {
        await Promise.reject(new Error("um erro ocorreu"));
    } catch(err) {
        console.log(err.message);
    }
}

funcao();

Note que o código fica mais fácil de ler e raramente usamos as chamadas encadeadas de then() e catch(). Com a introdução de funções assíncronas com async/await, a escrita de um código assíncrono fica parecido com a escrita de um código síncrono.

Agora que aprendemos como funciona o async/await, podemos refatorar nosso código para utilizar este recurso:

function fazChamada(){
    console.log("eu faço a chamada");
}

function depoisDe(delay) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const atendeu = Math.random() > 0.5;
            if(atendeu) {
                resolve("alô");
            } else {
                reject(new Error("a pessoa nao atendeu")); 
            }
        }, delay);
    });
}

async function ligacao() {
    fazChamada();
    try {
        const msg = await depoisDe(3000);
        console.log(msg);
    }catch(err) {
        console.log(err.message);
    }
}

ligacao();


This content originally appeared on DEV Community and was authored by thaisandre


Print Share Comment Cite Upload Translate Updates
APA

thaisandre | Sciencx (2021-05-11T01:35:30+00:00) Programação assíncrona. Retrieved from https://www.scien.cx/2021/05/11/programacao-assincrona/

MLA
" » Programação assíncrona." thaisandre | Sciencx - Tuesday May 11, 2021, https://www.scien.cx/2021/05/11/programacao-assincrona/
HARVARD
thaisandre | Sciencx Tuesday May 11, 2021 » Programação assíncrona., viewed ,<https://www.scien.cx/2021/05/11/programacao-assincrona/>
VANCOUVER
thaisandre | Sciencx - » Programação assíncrona. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/05/11/programacao-assincrona/
CHICAGO
" » Programação assíncrona." thaisandre | Sciencx - Accessed . https://www.scien.cx/2021/05/11/programacao-assincrona/
IEEE
" » Programação assíncrona." thaisandre | Sciencx [Online]. Available: https://www.scien.cx/2021/05/11/programacao-assincrona/. [Accessed: ]
rf:citation
» Programação assíncrona | thaisandre | Sciencx | https://www.scien.cx/2021/05/11/programacao-assincrona/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.