Pergunta

eu estou lendo Este artigo E a seção sobre a abstração da promessa parece um pouco excessivamente complicada para mim. O seguinte é dado como exemplo:

requestSomeData("http://example.com/foo") // returns a promise for the response
    .then(function(response){ // ‘then’ is used to provide a promise handler
        return JSON.parse(response.body); // parse the body
    }) // returns a promise for the parsed body
    .then(function(data){
        return data.price; // get the price
    }) // returns a promise for the price
    .then(function(price){ // print out the price when it is fulfilled
        print("The price is " + price);
    });

Parece -me que o seguinte pode fornecer o mesmo resultado com menos linhas de código:

requestSomeData("http://example.com/foo")
    .requestHandler(function(response){
        // parse the body
        var data  = JSON.parse(response.body);

        // get the price
        var price = data.price;

        // print out the price
        print("The price is " + price);
    });
Foi útil?

Solução

Embora seja verdade que ambos acabarão realizando a mesma coisa, a diferença é que seu segundo exemplo não é assíncrono. Por exemplo, considere o que acontece se JSON.parse(...) acaba sendo uma operação extremamente cara; Você terá que pendurar até que tudo esteja terminado, o que nem sempre é o que você deseja.

É isso que as promessas o levam: a poderosa capacidade de adiar o cálculo da resposta certa até um momento mais conveniente. Como o nome sugere, o construto "promessa" para dar o resultado em algum momento, mas não necessariamente agora. Você pode ler mais sobre futuros e promete trabalhar em uma escala maior aqui.

Outras dicas

Vamos comparar o exemplo da promessa com um exemplo puro de JavaScript:

// First we need a convenience function for W3C's fiddly XMLHttpRequest.
// It works a little differently from the promise framework.  Instead of 
// returning a promise to which we can attach a handler later with .then(),
// the function accepts the handler function as an argument named 'callback'.

function requestSomeDataAndCall(url, callback) {
    var req = new XMLHttpRequest();
    req.onreadystatechange = resHandler;
    req.open("GET", url, false);
    req.send();
    function resHandler() {
        if (this.readyState==4 && this.status==200) {
            callback(this);
        } else {
            // todo: Handle error.
        }
    }
}

requestSomeDataAndCall("http://example.com/foo", function(res){
    setTimeout(function(){
        var data = JSON.parse(res.responseText);
        setTimeout(function(){
            var price = data.price;
            setTimeout(function(){
                print("The price is "+price);
            },10);
        },10);
    },10);
});

Como Norbert Hartl apontou, Json.parse () pendurará o navegador para cordas grandes. Por isso, usei o setTimeout () para adiar sua execução (após uma pausa de 10 milissegundos). Este é um exemplo da solução de Kris Kowal. Ele permite que o thread JavaScript atual seja concluído, liberando o navegador para apresentar alterações no DOM e role a página para o usuário, antes do execução do retorno de chamada.

Espero que a estrutura do CommonJS Promise também use algo como o Settimeout, caso contrário, as promessas posteriores no exemplo do artigo realmente funcionam de maneira síncrona como temida.

Minha alternativa acima parece muito feia, com os processos posteriores exigindo um despacho adicional. Reestruturei o código, para que possamos fornecer nossa cadeia de processos em um nível:

function makeResolver(chain) {
    function climbChain(input) {
        var fn = chain.shift();      // This particular implementation
        setTimeout(function(){       // alters the chain array.
            var output = fn(input);
            if (chain.length>0) {
                climbChain(output);
            }
        },10);
    }
    return climbChain;
}

var processChain = [
    function(response){
        return JSON.parse(response.body);
    },
    function(data){
        return data.price; // get the price
    },
    function(price){
      print("The price is " + price);
    }
];

var climber = makeResolver(promiseChain);
requestSomeDataAndCall("http://example.com/foo", climber);

Eu esperava demonstrar que a passagem tradicional de retornos de chamada em JavaScript é praticamente equivalente às promessas. No entanto, depois de duas tentativas, parece ter mostrado, com referência à limpeza do código no exemplo original, as promessas são uma solução muito mais elegante!

O segundo snippet é vulnerável à negação de ataques de serviço, porque exemplo.com/foo pode apenas retornar o JSON inválido para travar o servidor. Até a resposta vazia é inválida JSON (embora JS válida). É como mysql_* Exemplos com furos de injeção de SQL.

E o código da promessa também pode ser melhorado. Estes são iguais:

requestSomeData("http://example.com/foo") // returns a promise for the response
    .then(function(response){ // ‘then’ is used to provide a promise handler
        // parse the body
        var data  = JSON.parse(response.body);

        // get the price
        var price = data.price;

        // print out the price
        print("The price is " + price);
    });

E:

requestSomeData("http://example.com/foo")
    .requestHandler(function(response){
        try {
            var data = JSON.parse(response.body);
        }
        catch(e) {
            return;
        }

        // get the price
        var price = data.price;

        // print out the price
        print("The price is " + price);
    });

Se quiséssemos lidar com o erro, estes seriam iguais:

requestSomeData("http://example.com/foo") // returns a promise for the response
    .then(function(response){ // ‘then’ is used to provide a promise handler
        // parse the body
        var data  = JSON.parse(response.body);

        // get the price
        var price = data.price;

        // print out the price
        print("The price is " + price);
    }).catch(SyntaxError, function(e) {
        console.error(e);
    });

e:

requestSomeData("http://example.com/foo")
    .requestHandler(function(response){
        try {
            var data = JSON.parse(response.body);
        }
        catch(e) {
            //If the above had a typo like `respons.body`
            //then without this check the ReferenceError would be swallowed
            //so this check is kept to have as close equality as possible with
            //the promise code
            if(e instanceof SyntaxError) {
                console.error(e);
                return;
            }
            else {
                throw e;
            }
        }

        // get the price
        var price = data.price;

        // print out the price
        print("The price is " + price);
    });

Pode-se também acrescentar que a vantagem da primeira versão no segundo é que ela separa diferentes operações na cadeia de refinamento (as funções também não precisam ser escritas no local). A segunda versão mistura a análise de baixo nível com a lógica do aplicativo. Especificamente, usando os princípios sólidos como diretrizes, a segunda versão viola os dois OCP e Srp.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top