fix: combine request throttling logic (#1568)
This commit is contained in:
parent
8b3842f15a
commit
89265f709e
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
34
src/routes/_utils/RequestThrottler.js
Normal file
34
src/routes/_utils/RequestThrottler.js
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue