В чем выгода абстракции «обещания» в CommonJS? - PullRequest
13 голосов
/ 29 января 2010

Я читаю эту статью , и раздел об абстракции обещаний кажется мне слишком сложным. В качестве примера приводится следующее:

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);
    });

Мне кажется, что следующий код может дать тот же результат с меньшим количеством строк кода:

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);
    });

Ответы [ 4 ]

17 голосов
/ 29 января 2010

Хотя верно и то, что оба в конечном итоге достигнут одного и того же, разница в том, что ваш второй пример не асинхронный. Например, рассмотрим, что произойдет, если JSON.parse(...) окажется чрезвычайно дорогой операцией; вам придется повесить, пока все не закончится, что не всегда может быть тем, что вы хотите.

Вот что вам обещает: мощная способность отложить вычисление правильного ответа до более удобного времени. Как следует из названия, конструкция «обещает» дать вам результат в какой-то момент, но не обязательно прямо сейчас. Вы можете прочитать больше о фьючерсах и обещаниях работать в большем масштабе здесь .

3 голосов
/ 19 ноября 2011

Давайте сравним пример обещания с чистым примером 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);
});

Как отметил Норберт Хартл, JSON.parse () повесит браузер для больших строк. Поэтому я использовал setTimeout (), чтобы задержать его выполнение (после паузы в 10 миллисекунд). Это один из примеров решения Криса Ковала. Он позволяет завершить текущий поток Javascript, освобождая браузер для представления изменений DOM и прокрутки страницы для пользователя до запуска обратного вызова.

Я надеюсь, что в структуре обещаний commonjs также используется что-то вроде setTimeout, в противном случае последующие обещания в примере статьи будут действительно работать синхронно, как и опасалось.

Моя альтернатива, представленная выше, выглядит довольно уродливо, поскольку последующие процессы требуют дополнительного отступа. Я реструктурировал код, чтобы мы могли предоставить нашу цепочку процессов на одном уровне:

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);

Я надеялся продемонстрировать, что традиционная пересылка обратных вызовов в Javascript в значительной степени эквивалентна обещаниям. Однако после двух попыток, как мне показалось, со ссылкой на аккуратность кода в исходном примере, эти обещания являются гораздо более элегантным решением!

1 голос
/ 09 ноября 2013

Второй фрагмент уязвим для атаки типа «отказ в обслуживании», потому что example.com/foo может просто вернуть недопустимый json для сбоя сервера. Даже пустой ответ является недействительным JSON (хотя и действительным JS). Это похоже на mysql_* примеры с явными отверстиями для SQL-инъекций.

И код обещания может быть значительно улучшен. Они равны:

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);
    });

И

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);
    });

Если бы мы хотели обработать ошибку, то они были бы равны:

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);
    });

и

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);
    });
0 голосов
/ 23 января 2013

Можно также добавить, что преимущество первой версии над второй заключается в том, что она разделяет различные операции в цепочке уточнения (функции также не должны записываться на месте). Вторая версия смешивает низкоуровневый анализ с логикой приложения. В частности, используя принципы SOLID в качестве руководства, вторая версия нарушает как OCP , так и SRP .

...