Async type ahead input buffer or queue

2019-09-13 12:18发布

问题:

I'm working on a type ahead input component and part of my implementation is a queue or buffer FIFO of cancelable promises.

Because the typing can happen much faster than the async requests on each letter, I have to cancel the previous request, so that state is not inadvertently changed if the previous promise is slower than the current.

My current implementation works well, but I'm wondering if its necessary to maintain a queue vs. a single reference. The queue should never really grow to retain more than one promise at a time, right?

_onChangeEvent(str) {

        var self = this;
        var str = String(str) || '';
        var toBeCanceled = null;

        // set state return false if no str
        // and shift off last request
        if (!str) {
            if (this.requestQueue.length > 0) {
                toBeCanceled = this.requestQueue.shift();
                toBeCanceled.cancel();
            }
            this.setState({results: [], loading: false, value: ''});
            return false;

        } else {
            this.setState({loading: true, value: str});
        }

        // setup a cancelable promise and add it to the queue
        // this API spoofer should eventually be a dispatch prop
        var cancelable = CancelablePromise(APISpoofer(str));

        cancelable
        .promise
        .then((res) => {
            var sorted = res.found.sort((a, b) => {
                var nameA = a.toLowerCase(), nameB = b.toLowerCase();
                if(nameA < nameB) return -1;
                if(nameA > nameB) return 1;
                return 0;
            }).slice(0, 4);

            // set state and shift queue when resolved
            return self.setState({
                results: sorted,
                loading: false,
                value: str
            }, () => {
                self.requestQueue.shift();
            });
        })
        .catch((err) => {
            return err;
        });

        // check len of requestQueue and push
        if(this.requestQueue.length === 0) {

            this.requestQueue.push(cancelable);

        } else {

            // cancel previous and push
            toBeCanceled = this.requestQueue.shift();
            toBeCanceled.cancel();
            this.requestQueue.push(cancelable);

        }

    }

Is there a problem with maintaining a queue of one value? Also, is there a name for this kind of problem?

回答1:

As your code immediately cancels any in-flight request before sending a new one, your queue will never contain more than one element. So it's useless to use a queue, a simple currentRequest field is enough to ensure you can handle only the most recent request, no matter in what order they are processed.

A common practice in type-ahead controls is to throttle the input events, i.e. wait a short amount of time for another change before actually sending an AJAX request. This avoids sending too many requests when users type several letters in a quick fashion.

Both of those problems can be abstracted from your code if you are willing to use reactive programming techniques, e.g. using RxJS http://reactivex.io/.

In fact somewhere down on the following RxJS page you will find an example that does exactly that: observing input changes, requiring at least 2 chars, debouncing, querying a webservice and then handling the results: https://github.com/Reactive-Extensions/RxJS