I fell in a Callbackhell?

Asked

Viewed 62 times

1

My class is consuming a weather webservice through the method _interceptaClima(). Notice that I’m treating the answer through callbacks.

The problem is that in the method calculaDesafio(), where I effectively implement the rule according to the answer, I need to return a value but am not able to deal with this scope problem (I tried to declare the challenge variable before but obviously failed because the scope is different).

How to return the/a result obtained inside the callback at the return of the function that is encapsulating it? I thought and tried to apply the use of Reflect but I don’t think that’s the case.

I think it might be an interesting question to address concepts about scope, callbacks, etc..

Thank you!

PS. I researched about Promises and I think it may be a way out but I wanted to check the problem in this current context.

class DiariaDinamica {

    constructor(objetivoMensal, valorVendido) {
        
        this.data = new Date();
        this.objetivoMensal = objetivoMensal;
        this.valorVendido = valorVendido;
        this._valorFaltante = this.objetivoMensal - this.valorVendido;
        this._diariaProporcional = this._valorFaltante / this._diasRestantes();
    };
    

    calculaDesafio() {
        
        let desafio;
        this._consultaClima(respostaConsulta => {  // respostaConsulta é um booleano
            if (respostaConsulta) {
                desafio = this._diariaProporcional * 1.10;
            };
        });

        return desafio;  // retorna undefined
    };


    _interceptaClima(callback) {

        ClimaService.consultaClima((err, id) => {

            if (err) {
                console.log(err); 
                return
            } 
            
            callback(id);
        }); 

        
    };


    _consultaClima(callback) {
        
        this._interceptaClima((id) => {

            let respostaConsulta = this._climaFavoravel().includes(id)  
            
            callback(respostaConsulta);
        });
        
    };


    _climaFavoravel() {

        const listaDeId = [
            200, 201, 202, 210, 211, 212, 221, 230, 231, 232,
            300, 301, 302, 310, 311, 312, 313, 314, 321,
            500, 501, 502, 503, 520, 522, 531, 504,
            800, 801, 802, 804 // Tirar o 800, 801, 802;
        ];

        return listaDeId;
    };   
};

FOLLOWS THE VIEW CODE:

class ResultadoView {

    constructor() {
        let $ = document.querySelector.bind(document)
        this.divFormulario = $('.formulario')
        this.divResposta = $('#principal')
        this.divAreaSugestao = $('#sugestao')
    }

    
    update(novaDiaria) {

        this.divResposta.innerHTML = this._templateResultados(novaDiaria)
    };

    
    _templateResultados(novaDiaria) {

        let data = new Date()
        return ` 

        <table id="tabelaResultados">
        <thead></thead>
        <tr>
            <td colspan='2'>${data.getDate()}/${data.getMonth() + 1}<img src='_assets/calendario-oficial.png' id='calendario-mini'></td>
            <td rowspan='6' id='cadastre-se' style='text-align: center'><button type='submit' id='btn-cadastrar'>Cadastre-me</button></td>
        </tr>
        <tr>
            <td>Desafio</td>
            <td>${novaDiaria.calculaDesafio().toLocaleString('pt-BR', {style: 'currency', 'currency': 'BRL'}) : ''}</td>
        </tr>
        <tr>
            <td>Diária proporcional</td>
            <td>${novaDiaria.diariaProporcional.toLocaleString('pt-BR', {style: 'currency', 'currency': 'BRL'})}</td>
        </tr>
        <tr>
            <td>Mínimo (85%)</td>
            <td>${novaDiaria.calculaMinima().toLocaleString('pt-BR', {style: 'currency', 'currency': 'BRL'})}</td>
        </tr>
        <tr>
            <td>Falta pra bater</td>
            <td>${novaDiaria.valorFaltante.toLocaleString('pt-BR', {style: 'currency', 'currency': 'BRL'})}</td>
        </tr>
        <tr id='projecao'>
            <td>Projeção atual (%)</td>
            <td>${novaDiaria.calculaProjecao().toFixed(2)}%</td>
        </tr>
        </table>
        `
    };
  • You can show where and what you’re calling the method calculaDesafio ?

  • @Sergio edited the question brother. Note that I call the method calculatedDesafio() in one of the Tds

1 answer

3


Your code problem is not a scope problem.

A callback can access variables declared at higher levels of the scope, the problem of your code is in the order in which the code is executed.

calculaDesafio() {
    
    let desafio;
    this._consultaClima(respostaConsulta => {
        if (respostaConsulta) {
            desafio = this._diariaProporcional * 1.10;
        };
    });

    return desafio;  // retorna undefined
};

In this section, the call of the method _consultaClima will eventually result in the call of the method ClimaService.consultaClima, which I believe to be an input/output operation. This type of operation results in a lag time, which your process does not wait for resolution.

Alias, it is for this reason that the code uses callbacks instead of being written imperatively, you pass a function to be executed during the resolution of your request, and meanwhile the rest of your code can continue to be executed, without the main process getting stuck waiting for an answer.

So what happens here is this:

  • desafio is declared
  • you invoke _consultaClima passing a callback
  • _consultaClima eventually invokes ClimaService.consultaClima
  • before ClimaService.consultaClima resolve, the process resumes executing the rest of the instructions
  • desafio is returned by its function before being initialized, because the callback has not yet been executed

So as you said yourself, a way out would be to use a Promise.

Promise is an object with some methods to help you treat these processes of asynchronous nature. You return Promise in a pending state, and then invoke a method by passing a callback to be executed when the pending process resolves.

calculaDesafio() {

    return new Promise((resolve, reject) => {
       this._consultaClima((respostaConsulta) => {
           if (respostaConsulta) { resolve(this._diariaProporcional * 1.10); }
           else { reject(); }
       });
    });

}

And then you can take and treat the return of calculaDesafio as follows:

diariaDinamica.calculaDesafio().then(resultado => {
    console.log(resultado);
    // restante do seu código
});

But that didn’t get you out of callbacks, did it? What’s the motivation for using it?

There is another abstraction you can use with Precedents, the modifier async and the operator await.

async and await allows you to treat Promises imperatively. A function declared with the modifier async automatically wraps your return in a file, and the operator await invokes the method then and returns the result on the same line as follows:

async calculaDesafio() {
    // _consultaClima precisa retornar uma promise
    const respostaConsulta = await this._consultaClima();
    if (respostaConsulta) {
        return this._diariaProporcional * 1.10;
    }
}

And then

const resultado = await diariaDinamica.calculaDesafio();

async and await makes the code more readable, but personally I don’t think it’s such a simple concept to explain. It is worth reading more about before using them.

  • excellent! I will go deeper into these concepts, but I found another solution changing the architecture of the application. I passed the call from the webservice to the controller, then inside the callback I call the view update, after I have the answer ready. One thing that was making it difficult was to call the API inside the model and not in the controller..

Browser other questions tagged

You are not signed in. Login or sign up in order to post.