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