Debounced Type-Ahead Search

This advanced example combines multiple ember-concurrency concepts to build a basic type-ahead search field with the following features:

  • Debouncing: the browser won't make network requests until the user has stopped typing for more than 250ms. This is accomplished by combining the .restartable() task modifier with a yield timeout(250) at the beginning of the task.
  • XHR cancelation: if the user starts typing while a prior XHR request is underway, that XHR request will be canceled to save network resources (this is accomplished via the try / finally cancelation pattern).
  • Use Derived State to display both a loading spinner and the final search results without using a single .set().
Live Example

(Please mind the GitHub API quota :)

const DEBOUNCE_MS = 250;
export default Controller.extend({
  searchRepo: task(function * (term) {
    if (isBlank(term)) { return []; }

    // Pause here for DEBOUNCE_MS milliseconds. Because this
    // task is `restartable`, if the user starts typing again,
    // the current search will be canceled at this point and
    // start over from the beginning. This is the
    // ember-concurrency way of debouncing a task.
    yield timeout(DEBOUNCE_MS);

    let url = `${term}`;

    // We yield an AJAX request and wait for it to complete. If the task
    // is restarted before this request completes, the XHR request
    // is aborted (open the inspector and see for yourself :)
    let json = yield this.get('getJSON').perform(url);
    return json.items.slice(0, 10);

  getJSON: task(function * (url) {
    let xhr;
    try {
      xhr = $.getJSON(url);
      let result = yield xhr.promise();
      return result;

      // NOTE: could also write this as
      // return yield xhr;
      // either way, the important thing is to yield before returning
      // so that the `finally` block doesn't run until after the
      // promise resolves (or the task is canceled).
    } finally {
<input type="text" oninput={{perform searchRepo value="target.value"}}
       placeholder="Search GitHub..." />

{{#if searchRepo.isRunning}}

  {{#each searchRepo.lastSuccessful.value as |repo|}}