Debounce playground
authorNicholas Bollweg (Nick) <nick.bollweg@gmail.com>
Mon, 12 May 2014 16:24:40 +0200
changeset 2152 829349eb856c
parent 2151 49751546940b
child 2153 8d5d3b4c41d0
child 2155 a12ff6c6c901
Debounce playground

Restores the per-keystroke delay, as discussed in #344, but only after a
failure, to maintain responsiveness under normal conditions.

Includes the content of es6-promise-debounce, and should eventually use
this (or whatever it ends being called) from CDN, pending release.

This closes #346 and fixes #344.

Squashed commit of the following:

commit c212de41634816c20878e61a031b8665d012f197
Author: Nicholas Bollweg (Nick) <[email protected]>
Date: Fri May 9 18:55:03 2014 -0400

adding updated debounce from @dlongley

commit 5ea32572a7b847f97ace024bdf429f151040acbb
Author: Nicholas Bollweg (Nick) <[email protected]>
Date: Fri May 9 00:59:19 2014 -0400

using es6 promise debouncing

commit 71e9995290feef2f7166edf04a5426d8b417dc5d
Author: Nicholas Bollweg (Nick) <[email protected]>
Date: Tue May 6 21:58:11 2014 -0400

#344 adding fetchRemote cache

commit 0c9a18e77066ee99586d6f2f5013bf7f18a6dbea
Author: Nicholas Bollweg (Nick) <[email protected]>
Date: Tue May 6 21:36:40 2014 -0400

#344: changing to a promise-ing debounce

commit b705bafe13ea2417456ffa16e760e08e95ce481c
Author: Nicholas Bollweg (Nick) <[email protected]>
Date: Tue May 6 21:16:58 2014 -0400

#344: adding real debounce of process and fetchRemote
playground/playground.js
--- a/playground/playground.js	Fri May 02 10:58:36 2014 -0400
+++ b/playground/playground.js	Mon May 12 16:24:40 2014 +0200
@@ -275,6 +275,45 @@
 
 
   /**
+   * return a debounced copy of a function
+   * thanks to @cwarden
+   * https://github.com/cwarden/promising-debounce/blob/master/src/debounce.js
+   *
+   * @param a function
+   * @param a number of milliseconds
+   *
+   * @return the function, which will only be called every `delay` milliseconds,
+   *         which will then, in turn, return a $.Deferred
+   */
+  playground.debounce = function(fn, wait, immediate) {
+    var timer = null;
+    return function() {
+      var context = this;
+      var args = arguments;
+      var resolve;
+      var promise = new Promise(function(_resolve) {
+        resolve = _resolve;
+      }).then(function() {
+        return fn.apply(context, args);
+      });
+      if(!!immediate && !timer) {
+        resolve();
+      }
+      if(timer) {
+        clearTimeout(timer);
+      }
+      timer = setTimeout(function() {
+        timer = null;
+        if(!immediate) {
+          resolve();
+        }
+      }, wait);
+      return promise;
+    };
+  };
+
+
+  /**
    * Initialize a CodeMirror editor
    *
    * @param a `<textarea>`
@@ -455,6 +494,10 @@
   };
 
 
+  // Cache of fetched remote urls
+  playground.fetchRemoteCache = {};
+  playground.fetchRemoteFails = {};
+
   /**
    * Fetch a remote document and populate an editor.
    *
@@ -462,27 +505,40 @@
    *
    * @return jQuery deferred, or `undefined`
    */
-  playground.fetchRemote = function(key){
+  playground.fetchRemote = playground._fetchRemote = function(key){
     if(!playground.useRemote[key]){ return; }
 
-    var btn = $("[data-editor=" + key + "] button");
-
-    return $.ajax({
-      url: playground.remoteUrl[key],
-      dataType: 'json',
-      crossDomain: true,
-      success: function(data) {
+    var btn = $("[data-editor=" + key + "] button"),
+      debounced = playground.fetchRemote !== playground._fetchRemote,
+      url = playground.remoteUrl[key],
+      hit = playground.fetchRemoteCache[url],
+      fail = playground.fetchRemoteFails[url],
+      success = function(data){
+        playground.fetchRemoteCache[url] = data;
         btn.addClass("btn-info active");
         // setValue always triggers a .process()
         playground.editors[key].setValue(playground.humanize(data));
-        return data;
+        playground.fetchRemote = playground._fetchRemote;
       },
-      error: function() {
+      error = function(err) {
+        playground.fetchRemoteFails[url] = err;
         btn.addClass("btn-danger active");
         $('#processing-errors')
            .text('Error loading ' + key + ' URL: ' + playground.remoteUrl[key]);
-      }
-    });
+        playground.fetchRemote = debounced ?
+          playground.fetchRemote :
+          playground.debounce(playground._fetchRemote, 500);
+      };
+
+    return hit ? success(hit) :
+      fail ? error(fail) :
+      $.ajax({
+        url: url,
+        dataType: 'json',
+        crossDomain: true,
+        success: success,
+        error: error
+      });
   };
 
 
@@ -675,7 +731,7 @@
    *
    * @return a promise to process
    */
-  playground.process = function(){
+  playground.process = playground._process = function(){
     $('#markup-errors').text('');
     $('#param-errors').text('');
     $('#processing-errors').text('');
@@ -728,16 +784,23 @@
       }
     }
 
+    var debounced = playground.process !== playground._process;
+
     // no errors, perform the action and display the output
     return playground.performAction(input, param)
       .then(
         function(){
           playground.permalink();
+          playground.process = playground._process;
         },
         function(err){
           // FIXME: add better error handling output
           $('#processing-errors').text(playground.humanize(err));
           playground.permalink(err);
+          playground.process = debounced ?
+            playground.process :
+            playground.debounce(playground._process, 500);
+          return err;
         }
       );
   };