В чем преимущество абстракции «обещания» в CommonJS?
-
23-09-2019 - |
Вопрос
я читаю Эта статья и раздел, посвященный абстракции обещаний, кажется мне слишком сложным.В качестве примера приведено следующее:
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);
});
Решение
Хотя это правда, что оба в конечном итоге выполнят одно и то же, разница в том, что ваш второй пример не является асинхронным.Например, рассмотрим, что произойдет, если JSON.parse(...)
оказывается чрезвычайно дорогостоящей операцией;вам придется зависать, пока все не будет закончено, что не всегда может быть тем, чего вы хотите.
Вот что вам принесут обещания:мощная способность откладывать вычисление правильного ответа до более удобного времени.Как следует из названия, конструкция «обещает» дать вам результат в какой-то момент, но не обязательно прямо сейчас.Вы можете узнать больше о фьючерсах и обещаниях, работающих в более широком масштабе. здесь.
Другие советы
Давайте сравним пример обещания с примером чистого 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 во многом эквивалентна обещаниям.Однако после двух попыток я, судя по аккуратности кода в исходном примере, показал, что промисы — гораздо более элегантное решение!
Второй фрагмент уязвим для атаки типа «отказ в обслуживании», поскольку 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);
});
Можно еще добавить, что преимущество первой версии перед второй состоит в том, что она разделяет разные операции в цепочке уточнений (функции также не обязательно писать на месте).Вторая версия сочетает низкоуровневый анализ с логикой приложения.В частности, используя принципы SOLID в качестве руководства, вторая версия нарушает оба ОКП и рекомендуемая розничная цена.