diff --git a/src/routes/_actions/autosuggestAccountSearch.js b/src/routes/_actions/autosuggestAccountSearch.js index 89e92714..783c8f76 100644 --- a/src/routes/_actions/autosuggestAccountSearch.js +++ b/src/routes/_actions/autosuggestAccountSearch.js @@ -5,10 +5,9 @@ import { SEARCH_RESULTS_LIMIT } from '../_static/autosuggest' import { concat } from '../_utils/arrays' import uniqBy from 'lodash-es/uniqBy' import { scheduleIdleTask } from '../_utils/scheduleIdleTask' -import { PromiseThrottler } from '../_utils/PromiseThrottler' +import { RequestThrottler } from '../_utils/RequestThrottler' const DATABASE_SEARCH_RESULTS_LIMIT = 30 -const promiseThrottler = new PromiseThrottler(200) // Mastodon FE also uses 200ms function byUsername (a, b) { const usernameA = a.acct.toLowerCase() @@ -26,31 +25,24 @@ export function doAccountSearch (searchText) { let localResults let remoteResults const { currentInstance, accessToken } = store.get() - let controller = typeof AbortController === 'function' && new AbortController() - - function abortFetch () { - if (controller) { - controller.abort() - controller = null - } - } + const requestThrottler = new RequestThrottler(searchAccountsRemotely, onNewRemoteResults) async function searchAccountsLocally (searchText) { localResults = await database.searchAccountsByUsername( currentInstance, searchText.substring(1), DATABASE_SEARCH_RESULTS_LIMIT) } - async function searchAccountsRemotely (searchText) { - // Throttle our XHRs to be a good citizen and not spam the server with one XHR per keystroke - await promiseThrottler.next() - if (canceled) { - return - } - remoteResults = (await search( - currentInstance, accessToken, searchText, false, SEARCH_RESULTS_LIMIT, controller && controller.signal + async function searchAccountsRemotely (signal) { + return (await search( + currentInstance, accessToken, searchText, false, SEARCH_RESULTS_LIMIT, signal )).accounts } + function onNewRemoteResults (results) { + remoteResults = results + onNewResults() + } + function mergeAndTruncateResults () { // Always include local results; they are more likely to be relevant // because the user has seen their content before. Otherwise, sort by username. @@ -87,18 +79,14 @@ export function doAccountSearch (searchText) { return } // run the two searches in parallel - searchAccountsLocally(searchText).then(onNewResults).catch(err => { - console.error('could not search locally', err) - }) - searchAccountsRemotely(searchText).then(onNewResults).catch(err => { - console.error('could not search remotely', err) - }) + searchAccountsLocally(searchText).then(onNewResults) + requestThrottler.request() }) return { cancel: () => { canceled = true - abortFetch() + requestThrottler.cancel() } } } diff --git a/src/routes/_actions/autosuggestHashtagSearch.js b/src/routes/_actions/autosuggestHashtagSearch.js index 8d0b0bb3..bd12b15c 100644 --- a/src/routes/_actions/autosuggestHashtagSearch.js +++ b/src/routes/_actions/autosuggestHashtagSearch.js @@ -2,32 +2,19 @@ import { search } from '../_api/search' import { store } from '../_store/store' import { scheduleIdleTask } from '../_utils/scheduleIdleTask' import { SEARCH_RESULTS_LIMIT } from '../_static/autosuggest' -import { PromiseThrottler } from '../_utils/PromiseThrottler' - -const promiseThrottler = new PromiseThrottler(200) // Mastodon FE also uses 200ms +import { RequestThrottler } from '../_utils/RequestThrottler' export function doHashtagSearch (searchText) { - let canceled = false const { currentInstance, accessToken } = store.get() - let controller = typeof AbortController === 'function' && new AbortController() + const requestThrottler = new RequestThrottler(searchHashtagsRemotely, onNewResults) - function abortFetch () { - if (controller) { - controller.abort() - controller = null - } + async function searchHashtagsRemotely (signal) { + return (await search( + currentInstance, accessToken, searchText, false, SEARCH_RESULTS_LIMIT, signal + )).hashtags } - async function searchHashtagsRemotely (searchText) { - // Throttle our XHRs to be a good citizen and not spam the server with one XHR per keystroke - await promiseThrottler.next() - if (canceled) { - return - } - const searchPromise = search( - currentInstance, accessToken, searchText, false, SEARCH_RESULTS_LIMIT, controller && controller.signal - ) - const results = (await searchPromise).hashtags + function onNewResults (results) { store.setForCurrentAutosuggest({ autosuggestType: 'hashtag', autosuggestSelected: 0, @@ -36,16 +23,12 @@ export function doHashtagSearch (searchText) { } scheduleIdleTask(() => { - if (canceled) { - return - } - /* no await */ searchHashtagsRemotely(searchText) + requestThrottler.request() }) return { cancel: () => { - canceled = true - abortFetch() + requestThrottler.cancel() } } } diff --git a/src/routes/_utils/RequestThrottler.js b/src/routes/_utils/RequestThrottler.js new file mode 100644 index 00000000..908ca132 --- /dev/null +++ b/src/routes/_utils/RequestThrottler.js @@ -0,0 +1,34 @@ +// Throttle network requests to be a good citizen and not issue an HTTP request on every keystroke +import { PromiseThrottler } from './PromiseThrottler' + +const promiseThrottler = new PromiseThrottler(200) // Mastodon FE also uses 200ms + +export class RequestThrottler { + constructor (fetcher, onNewResults) { + this._canceled = false + this._controller = typeof AbortController === 'function' && new AbortController() + this._fetcher = fetcher + this._onNewResults = onNewResults + } + + async request () { + if (this._canceled) { + return + } + await promiseThrottler.next() + if (this._canceled) { + return + } + const signal = this._controller && this._controller.signal + const results = await this._fetcher(signal) + this._onNewResults(results) + } + + cancel () { + this._canceled = true + if (this._controller) { + this._controller.abort() + this._controller = null + } + } +}