Solucionado (ver solução)
Solucionado
(ver solução)
3
respostas

[Callbacks] Quais as boas práticas e melhores maneiras de usar?

Boa tarde, pessoal

Como dito em meu ultimo post, estou utilizando o projeto desta aula como base para fazer minha própria API de pagamentos (estou trabalhando em uma start-up de meios de pagamento) e encontrei alguns problemas ao ir adiante com o desenvolvimento da solução. Sendo mais específico, meus problemas são as orientações a callback do Js e do NodeJs.

Vamos ao código:


transactionProcessor.prototype.transaction = function (paymentRequest, callback) {

    var err, finalResponse;
    err = finalResponse = null;

    console.log("============= TRANSACTION =============");

    this._authorize(paymentRequest, function (error, authResponse) {

        if (error) {
            //TODO: format the error here
            err = error;
            return;
        }

        if (authResponse.status !== "success") {
            //TODO: return that the authorization wasn't approved
            finalResponse = authResponse;
            return;
        }
    });

    console.log("============= TRANSACTION =============");

    callback(err, finalResponse);
};

transactionProcessor.prototype._authorize = function (clientRequest, callback) {

    console.log("============= AUTHORIZE =============");

    var formattedRequest = this._transformRequest(clientRequest, this._requestTypes.AUTHORIZATION);
    var reqOptions = {
        url: this._requestUrls.transaction,
        body: formattedRequest,
        headers: {'Content-Type': 'text/xml'}
    };

    var formattedError, serverResponse;
    formattedError = serverResponse = null;

    this._makeRequest(reqOptions, function (err, finalResponse) {

        if (err) {
            //TODO: format your error here
            formattedError = err;
            return;
        }
        serverResponse = finalResponse;
    });

    console.log("============= AUTHORIZE =============");

    callback(formattedError, serverResponse);
};

transactionProcessor.prototype._makeRequest = function (options, callback) {

    var err, finalResponse;
    err = finalResponse = null;

    console.log("============= MakeREQUEST =============");

    request.post(options, function (error, response, responseBody) {

        console.log("============= REQUEST LIB =============");

        console.log("Error: " + JSON.stringify(error));
        console.log("Response: " + JSON.stringify(response));
        console.log("Body: " + JSON.stringify(responseBody));

        if (error) {
            err = error;
            console.log("Error: " + JSON.stringify(error));
            return;
        }

        if (response.statusCode !== 200) {
            console.log("Response != 200: " + JSON.stringify(response));
            return;
        }

        finalResponse = responseBody;
        console.log("MaxxiPago Response: " + finalResponse);

        console.log("============= REQUEST LIB =============");
    });

    console.log("Err: " + err);
    console.log("finalResponse: " + finalResponse);

    console.log("============= MakeREQUEST =============");

    callback(err, finalResponse);
};

Como podemos ver acima, temos 3 funções que são:

  • transaction: esta é a função principal, fará todo o fluxo da transação como autorização (verificação de dados válidos), captura (se autorização não deu erro, faz a captura e aprova), guardar a transação (mysql) e enviar a resposta ao cliente.

  • _authorize: essa é a função responsável pela autorização dita acima, tem como objetivo pegar a requisição do cliente e transformar em uma requisição no formato que espera o processador de transação e retornar a resposta do mesmo

  • _makeRequest: essa é a função que realmente utiliza a lib request. Criei essa função com o intuito de não repetir código pois todas as requisições ao processador de transações (transação, estorno, recorrência, etc) são da mesma forma, mudando somente o corpo da requisição.

Dado o contexto, aqui vai o meu problema: Como podemos ver, a função makeRequest possui o callback para a função post da lib request. Até aí tudo bem, só que o problema é que as variáveis err e finalResponse (declaradas no começo como null para receberem valor dentro do callback da request) continuam sendo retornadas como null porquê a ordem de execução do node em relação a callbacks é assíncrona e o callback é executado depois da função que o chama.

Porém, eu necessito que aquelas variáveis sejam preenchidas.Pois se não forem, serão retornadas nulas as funções authorize e transaction e acabará dando erro como "Cannot read property 'status' of null (na função transaction)".

Obs: - Estou utilizando callback na função makeRequest e na Authorize porque foi a melhor maneira que encontrei de retornar as informações como err e responseBody. Há uma maneira melhor de se fazer isso?

Qual a resolução disso?

Atenciosamente, Caio Salgado Nepomuceno

3 respostas

Opa Caio, tudo bem? Eu não sei se entendi seu problema por completo, mas vou tentar te ajudar analisando apenas um dos trechos, tá bem?

Vamos lá, o problema é que você precisa dos valores das variáveis pra depois passá-las para os callbacks, mas sem correr o risco de tomar um erro tentando ler um valor de null. Correto?

Vejamos este trecho de código:

transactionProcessor.prototype.transaction = function (paymentRequest, callback) {

    var err, finalResponse;
    err = finalResponse = null;

    console.log("============= TRANSACTION =============");

    this._authorize(paymentRequest, function (error, authResponse) {

        if (error) {
            //TODO: format the error here
            err = error;
            return;
        }

        if (authResponse.status !== "success") {
            //TODO: return that the authorization wasn't approved
            finalResponse = authResponse;
            return;
        }
    });

    console.log("============= TRANSACTION =============");

    callback(err, finalResponse);
};

Temos duas situações aqui, certo? Se houver erro, você chama o callback com o erro e se o status foi diferente de success (outro erro) você retorna isso também. Certo?

Pelo seu return, as coisas são binárias um erro ou outro. Mas o caso de tudo dar certo? Acontece quando? Em lugar algum, neste caso, os dois valores continuação como NULL. Pegou o ponto?

Outra observação, estou vendo que você está usando o return bastante para encerrar execução de trechos de códigos. Lembra como ele funciona? Ele encerra a execução da função.

Isso quer dizer que neste trecho, o finalResponse não vai ser retornado:

 this._makeRequest(reqOptions, function (err, finalResponse) {

        if (err) {
            //TODO: format your error here
            formattedError = err;
            return;
        }
        serverResponse = finalResponse;
    });

O callback vai morrer ali no return e você não vai ter o serverResponse. Isso pode tá levando você a encontrar outros nulls que não deveria.

Oi Wanderson, tudo bem?

Este trecho de código foi só pra exemplificar que não importa o que aconteça no callback de authorize na função transaction ou no callback de makerequest na função authorize, as variáveis que são criadas fora como null e recebem valor dentro dos callbacks ainda serão nulas porque o Node executa primeiro a função até o fim e depois o callback no qual ela chamou, entendeu?

vou tentar exemplificar.

Na função authorize, por exemplo, a ordem de execução das coisas seria:

transactionProcessor.prototype._authorize = function (clientRequest, callback) {

    console.log("============= AUTHORIZE =============");

    var formattedRequest = this._transformRequest(clientRequest, this._requestTypes.AUTHORIZATION);
    var reqOptions = {
        url: this._requestUrls.transaction,
        body: formattedRequest,
        headers: {'Content-Type': 'text/xml'}
    };

    var formattedError, serverResponse;
    formattedError = serverResponse = null;

    this._makeRequest(reqOptions, function (err, finalResponse) {});

    console.log("============= AUTHORIZE =============");

    callback(formattedError, serverResponse);

    if (err) {
        //TODO: format your error here
                formattedError = err;
                return;
        }
        serverResponse = finalResponse;
};

Sendo assim, queria saber como funciona o callback e como usá-lo.

solução!

Oi Caio, o que eu quis dizer são basicamente duas coisas:

  1. No uso de callbacks, você não pode esperar a função executar linha a linha e executar o callback só no final. Você já sabe que a execução é assíncrona. A chamada do callback precisa ser dentro da função onde você está recebendo o valor que precisa para o callback, não depois. Por isso, você pode se ver chamando o mesmo callback várias vezes. É normal isso.

  2. O uso de return joga fora do fluxo geral, sai da função inteira, então não adianta atribuir um valor depois de um return, esse valor não será atribuído. o return não encerra um if, ele encerra uma função inteira.