Previous: Loading UI
This advanced example combines multiple ember-concurrency concepts to build a basic type-ahead search field with the following features:
restartable
task modifier
with a yield timeout(250)
at the beginning of the task.
.set()
.
Please mind the GitHub API quota :)
const DEBOUNCE_MS = 250; export default class AutocompleteController extends Controller { @restartableTask *searchRepo(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 = `https://api.github.com/search/repositories?q=${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.getJSON.perform(url); return json.items.slice(0, 10); } @task *getJSON(url) { let controller = new AbortController(); let signal = controller.signal; try { let response = yield fetch(url, { signal }); let result = yield response.json(); return result; // NOTE: could also write this as // return yield fetch(url, { signal }).then((response) => response.json()); // // 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 { controller.abort(); } } }
<label> Search GitHub... <input type="text" {{on "input" (perform this.searchRepo value="target.value")}} placeholder="e.g. machty/ember-concurrency"> </label> {{#if this.searchRepo.isRunning}} <LoadingSpinner /> {{/if}} <ul> {{#each this.searchRepo.lastSuccessful.value as |repo|}} <li>{{repo.full_name}}</li> {{/each}} </ul>
Previous: Loading UI