title: Understanding the Spring MVC's DeferredResult class in the context of the spring-mvc-chat github application
body: <p>I am trying to better understand how the following spring mvc 3.2 application works: <a href="https://github.com/rstoyanchev/spring-mvc-chat">https://github.com/rstoyanchev/spring-mvc-chat</a></p>
  
  <p>My question is about the <a href="http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/web/context/request/async/DeferredResult.html">deferredResult Spring MVC class</a>. I noticed that at a given time there are as many entries in the <code>chatRequests</code> Map as there are users connected to the chat application.</p>
  
  <p>Say there are <strong>3 users connected</strong> to the chat application. You will see that when <strong>user #3 posts a message</strong> (see postMessage method below), then <strong>the for loop (in postMessage method) iterates three times</strong>. I can't figure out why that is.</p>
  
  <p>I am including sample code below.</p>
  
  <p>Code for controller:</p>
  
  <pre><code>@Controller
  @RequestMapping("/mvc/chat")
  public class ChatController {
  
      private final ChatRepository chatRepository;
      private final Map&lt;DeferredResult&lt;List&lt;String&gt;&gt;, Integer&gt; chatRequests = new ConcurrentHashMap&lt;DeferredResult&lt;List&lt;String&gt;&gt;, Integer&gt;();
  
      @Autowired
      public ChatController(ChatRepository chatRepository) {
          this.chatRepository = chatRepository;
      }
  
      @RequestMapping(method = RequestMethod.GET)
      @ResponseBody
      public DeferredResult&lt;List&lt;String&gt;&gt; getMessages(@RequestParam int messageIndex) {
  
          final DeferredResult&lt;List&lt;String&gt;&gt; deferredResult = new DeferredResult&lt;List&lt;String&gt;&gt;(null, Collections.emptyList());
          this.chatRequests.put(deferredResult, messageIndex);
  
          deferredResult.onCompletion(new Runnable() {
              @Override
              public void run() {
                  chatRequests.remove(deferredResult);
              }
          });
  
          List&lt;String&gt; messages = this.chatRepository.getMessages(messageIndex);
          if (!messages.isEmpty()) {
              deferredResult.setResult(messages);
          }
  
          return deferredResult;
      }
  
      @RequestMapping(method = RequestMethod.POST)
      @ResponseBody
      public void postMessage(@RequestParam String message) {
  
          this.chatRepository.addMessage(message);
  
          // Update all chat requests as part of the POST request
          // See Redis branch for a more sophisticated, non-blocking approach
  
          for (Entry&lt;DeferredResult&lt;List&lt;String&gt;&gt;, Integer&gt; entry : this.chatRequests.entrySet()) {
              List&lt;String&gt; messages = this.chatRepository.getMessages(entry.getValue());
              entry.getKey().setResult(messages);
          }
      }
  }
  </code></pre>
  
  <p>Javascript code:</p>
  
  <pre><code>$(document).ready(function() {
  
      function ChatViewModel() {
  
          var that = this;
  
          that.userName = ko.observable('');
          that.chatContent = ko.observable('');
          that.message = ko.observable('');
          that.messageIndex = ko.observable(0);
          that.activePollingXhr = ko.observable(null);
  
  
          var keepPolling = false;
  
          that.joinChat = function() {
              if (that.userName().trim() != '') {
                  keepPolling = true;
                  pollForMessages();
              }
          }
  
          function pollForMessages() {
              if (!keepPolling) {
                  return;
              }
              var form = $("#joinChatForm");
  
  
              that.activePollingXhr($.ajax({url: form.attr("action"), type: "GET", data: form.serialize(), cache: false,
                  success: function(messages) {
                      console.log(messages);
                      for (var i = 0; i &lt; messages.length; i++) {
                          that.chatContent(that.chatContent() + messages[i] + "\n");
                          that.messageIndex(that.messageIndex() + 1);
                      }
                  },
                  error: function(xhr) {
                      if (xhr.statusText != "abort" &amp;&amp; xhr.status != 503) {
                          resetUI();
                          console.error("Unable to retrieve chat messages. Chat ended.");
                      }
                  },
                  complete: pollForMessages
              }));
              $('#message').focus();
          }
  
          that.postMessage = function() {
              if (that.message().trim() != '') {
                  var form = $("#postMessageForm");
                  $.ajax({url: form.attr("action"), type: "POST",
                      data: "message=[" + that.userName() + "] " + $("#postMessageForm input[name=message]").val(),
                      error: function(xhr) {
                          console.error("Error posting chat message: status=" + xhr.status + ", statusText=" + xhr.statusText);
                      }
                  });
                  that.message('');
              }
          }
  
          that.leaveChat = function() {
              that.activePollingXhr(null);
              resetUI();
              this.userName('');
          }
  
          function resetUI() {
              keepPolling = false;
              that.activePollingXhr(null);
              that.message('');
              that.messageIndex(0);
              that.chatContent('');
          }
  
      }
  
      //Activate knockout.js
      ko.applyBindings(new ChatViewModel());
  
  });
  </code></pre>
  
  <p>and html page:</p>
  
  <pre><code>&lt;!DOCTYPE html&gt;
  &lt;html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"&gt;
  &lt;head&gt;
      &lt;title&gt;Chat&lt;/title&gt;
      &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /&gt;
  &lt;/head&gt;
  &lt;body&gt;
      &lt;h1&gt;Chat&lt;/h1&gt;
  
      &lt;form id="joinChatForm" th:action="@{/mvc/chat}" data-bind="visible: activePollingXhr() == null"&gt;
          &lt;p&gt;
              &lt;label for="user"&gt;User: &lt;/label&gt;
              &lt;input id="user" name="user" type="text" data-bind="value: userName"/&gt;
              &lt;input name="messageIndex" type="hidden" data-bind="value: messageIndex"/&gt;
              &lt;button id="start" type="submit" data-bind="click: joinChat"&gt;Join Chat&lt;/button&gt;
          &lt;/p&gt;
      &lt;/form&gt;
  
      &lt;form id="leaveChatForm" th:action="@{/mvc/chat}" data-bind="visible: activePollingXhr() != null"&gt;
          &lt;p&gt;
              You're chatting as &lt;strong data-bind="text: userName"&gt;&lt;/strong&gt;
              &lt;button id="leave" type="submit" data-bind="click: leaveChat"&gt;Leave Chat&lt;/button&gt;
          &lt;/p&gt;
      &lt;/form&gt;
  
      &lt;div data-bind="visible: activePollingXhr() != null"&gt;
          &lt;textarea rows="15" cols="60" readonly="readonly" data-bind="text: chatContent"&gt;&lt;/textarea&gt;
      &lt;/div&gt;
  
      &lt;form id="postMessageForm" th:action="@{/mvc/chat}" data-bind="visible: activePollingXhr() != null"&gt;
          &lt;p&gt;
              &lt;input id="message" name="message" type="text" data-bind="value: message" /&gt;
              &lt;button id="post" type="submit" data-bind="click: postMessage"&gt;Post&lt;/button&gt;
          &lt;/p&gt;
      &lt;/form&gt;
  &lt;/body&gt;
  &lt;script type="text/javascript" src="../../../resources/js/jquery-1.7.2.min.js" th:src="@{/resources/js/jquery-1.7.2.min.js}"&gt;&lt;/script&gt;
  &lt;script type="text/javascript" src="../../../resources/js/knockout-2.0.0.js" th:src="@{/resources/js/knockout-2.0.0.js}"&gt;&lt;/script&gt;
  &lt;script type="text/javascript" src="../../../resources/js/chat.js" th:src="@{/resources/js/chat.js}"&gt;&lt;/script&gt;
  
  &lt;/html&gt;
  </code></pre>