2010-01-29 50 views
13

我正在閱讀this article,關於承諾抽象的部分對我來說似乎有些過於複雜。下面給出一個例子:CommonJS中「承諾」抽象的好處是什麼?

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

你是對的,使用承諾進行同步操作沒有意義。所以結果應該是平等的。但這是一個例子,並說明了諾言的使用。對於你的例子後運行的代碼確實有區別。如果您需要在示例之後運行某些內容(無需知道示例代碼的作用) – 2011-03-21 08:05:45

回答

17

雖然這是事實,雙方最終完成同樣的事情,區別在於你的第二個例子不是異步的。例如,考慮如果JSON.parse(...)結果是非常昂貴的操作會發生什麼;你必須堅持到一切都完成了,這可能並不總是你想要的。

這就是承諾帶給你的感受:強大的能力推遲正確答案的計算,直到更方便的時間。顧名思義,構造「承諾」在某個時刻給你結果,但不一定是現在。你可以閱讀更多關於期貨和承諾的更大規模的工作here

+2

這裏的方便時間是什麼?如果操作非常昂貴並且JSON.parse是一段JavaScript代碼,它仍然會掛起。區別僅在於承諾您可以完成實際運行的功能。 – 2011-03-21 07:46:18

+3

的確,解析將花費相同的時間量,無論它是同步計算還是異步計算。但是,如果您花時間實現解析器的方式可以在完成小部分操作後以可預測的方式生成事件循環,則其他異步代碼可以在每個塊之間異步運行。這使得應用程序響應更快,而不是更快。 – 2011-09-23 18:32:36

+0

或JSON.parse可能是一個本地方法,並在另一個線程上執行 – Jan 2011-10-23 16:16:38

3

讓我們的承諾例子比較一個純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毫秒的停頓之後)。這是Kris Kowal解決方案的一個例子。它允許當前的Javascript線程完成,在回調運行之前釋放瀏覽器以呈現DOM更改併爲用戶滾動頁面。

我希望commonjs promise框架也使用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回調的傳統向前傳球就相當於承諾漂亮多了。然而,經過兩次嘗試,我發現,在原始示例中參考代碼的整潔性,這些承諾是一個更加優雅的解決方案!

0

有人可能會補充說,第一個版本比第二個版本的優勢在於它將細化鏈中的不同操作分開(函數不必在原地寫入)。第二個版本將低級解析與應用程序邏輯混合在一起。具體而言,使用SOLID原則作爲準則,第二個版本同時違反了OCPSRP

1

第二個片段容易遭受拒絕服務攻擊,因爲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); 
    });