next() {
    let token = this.currentBatch.token
    const remaining = this.currentBatch.remaining

    if (remaining <= 0 || !token.isValid || token.isExhausted) {
      token = this._nextBatch()
      this.currentBatch = {
        token,
        remaining: token.hasReset
          ? this.batchSize
          : Math.min(this.batchSize, token.usesRemaining),
      }
      this.fifoQueue.push(token)
    }

    this.currentBatch.remaining -= 1

    return token
  }
Obtain the next available token, returning `null` if no tokens are
available.

Tokens are initially pulled from a FIFO queue. The first token is used
for a batch of requests, then returned to the queue to give those
requests the opportunity to complete. The next token is used for the next
batch of requests.

This strategy allows a token to be used for concurrent requests, not just
sequential request, and simplifies token recovery after errored and timed
out requests.

By the time the original token re-emerges, its requests should have long
since completed. Even if a couple them are still running, they can
reasonably be ignored. The uses remaining won't be 100% correct, but
that's fine, because Shields uses only 75%

The process continues until an exhausted token is pulled from the FIFO
queue. At that time it's placed in the priority queue based on its
scheduled reset time. To ensure the priority queue works as intended,
the scheduled reset time is frozen then.

After obtaining a token using `next()`, invoke `update()` on it to set a
new use-remaining count and next-reset time. Invoke `invalidate()` to
indicate it should not be reused.

@returns {module:core/token-pooling/token-pool~Token} token