-
-
Save gmarik/5186b7a29ec48936dc10 to your computer and use it in GitHub Desktop.
Autocompletion and Request cancellation http://www.gmarik.info/blog/2016/autocompletion-request-cancellation-jquery-and-go/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> <script src="https://code.jquery.com/jquery-2.2.0.min.js" type="text/javascript"></script> | |
</head> | |
<body> | |
<form action="/search"> | |
<input type="text" name="q" autocomplete="off" placeholder="Search" /> | |
</form> | |
<pre id="result"> </pre> | |
<script> | |
// basic implementation | |
var AutoComplete = function(searcher) { | |
var search = {}; // state | |
this.value = function() { return search.value; } | |
this.search = function(value) { | |
// abort ongoing request if any | |
if (search.promise) { search.promise.abort() } | |
var promise = searcher(value) | |
search = {promise: promise, value: value} | |
promise.always(function() { search = {} }) | |
return promise; | |
} | |
} | |
// throttles `fn` calls for at least `timeout` | |
var defer = function(timeout, fn) { | |
var id; // state; if set then throttle is in progress | |
return function(e) { | |
if (id) { clearTimeout(id) } | |
id = setTimeout(function() { dfr = null; return fn(e) }, timeout) | |
} | |
} | |
var $results = $('#result'), | |
$form = $('form'), | |
$q = $form.find('input[name=q]'), | |
$searcher = function(value) { return $.get($form.attr('action'), {q: value}) }, | |
ac = new AutoComplete($searcher); | |
// abort has to capture search value being aborted | |
var abort = function(value) { | |
return function(_, kind) { | |
if ("abort" != kind) {return}; | |
$results.text($results.text() + "\naborting search for:" + value) | |
} | |
} | |
// app logic | |
var doSearch = function(value) { | |
// do not search for blank value | |
if ("" == value) { return } | |
// also skip if input value hasn't changed | |
if (ac.value() == value) { return } | |
$results.text("\nsearching for:" + value) | |
ac.search(value) | |
.then(function(responseText) { $results.text(responseText) }) | |
.fail(abort(ac.value())) | |
} | |
// entry point | |
$(function() { | |
$q.focus() | |
$q.on('keyup', defer(400, function(e) { doSearch(e.target.value) })) | |
$('form').on('submit', function(e) { e.preventDefault(); doSearch($q.val()) }) | |
}) | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"io/ioutil" | |
"log" | |
"net/http" | |
"time" | |
) | |
// Running example: | |
// $ go run searver.go | |
// $ open http://localhost:3030 | |
func main() { | |
http.HandleFunc("/search", handleSearch) | |
http.HandleFunc("/", handleIndex) | |
log.Fatal(http.ListenAndServe(":3030", nil)) | |
} | |
// handleSearch handles search/ search cancellation requests | |
func handleSearch(w http.ResponseWriter, r *http.Request) { | |
var ( | |
// abort signals other goroutine that any work needs not to be completed | |
abort = make(chan struct{}) | |
result = make(chan error) | |
q = r.FormValue("q") | |
log = func(str string) { fmt.Printf("[%s] %s\n", q, str) } | |
) | |
log("starting") | |
// search goroutine | |
go func() { | |
log("searching...") | |
result <- pseudoSearch(w, q, abort) | |
log("complete") | |
}() | |
select { | |
// assuming successful type assertion | |
//client diconnected | |
case <-w.(http.CloseNotifier).CloseNotify(): | |
close(abort) // signal disconnection | |
case <-result: | |
// search completed | |
} | |
log("done") | |
} | |
// pseudoSearch is an example pseudo search implementation | |
// it renders 40 lines prefixed with a query string | |
// rendering of each line is slowed down with 100ms delay to resemble some latency | |
func pseudoSearch(w http.ResponseWriter, q string, abort <-chan struct{}) error { | |
for i := 0; i < 40; i += 1 { | |
select { | |
case <-time.After(100 * time.Millisecond): | |
fmt.Fprintln(w, q, "result", i) | |
case <-abort: | |
fmt.Printf("[%s] abort!\n", q) | |
return nil | |
} | |
} | |
return nil | |
} | |
// handleIndex reads `client.html` and renders it to a user-agent/browser | |
func handleIndex(w http.ResponseWriter, r *http.Request) { | |
html, err := ioutil.ReadFile("client.html") | |
if err != nil { | |
http.Error(w, err.Error(), http.StatusServiceUnavailable) | |
return | |
} | |
fmt.Fprint(w, string(html)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment