insertAfter(data, index) {
    
        /*
         * Create a new list item object and store the data in it.
         * This item will be inserted into the existing list.
         */
        const newNode = new CircularDoublyLinkedListNode(data);

        // special case: no nodes in the list yet
        if (this[head] === null) {
            throw new RangeError(`Index ${index} does not exist in the list.`);
        }

        /*
         * The `current` variable is used to track the node that is being
         * used inside of the loop below. It starts out pointing to
         * `this[head]` (the first node) and is overwritten inside of the loop.
         */
        let current = this[head];

        // special case: insert after index 0 doesn't require a traversal
        if (index > 0) {

            /*
             * The `i` variable is used to track how deep into the list we've
             * gone. This important because it's the only way to know when
             * we've hit the `index` to insert into.
             */
            let i = 0;

            /*
             * Traverse and keep track of how many nodes have been visited and
             * update the `current` pointer. When `i` is the same as `index`, 
             * it means we've found the location to insert the new data.
             */
            do {
                current = current.next;
                i++;
            } while ((current !== this[head]) && (i < index));

            /*
             * At this point, `current` is either the node to insert the new data
             * before, or the last node in the list. The only way to tell is if
             * `i` is still less than `index`, that means the index is out of range
             * and an error should be thrown.
             */
            if (i < index) {
                throw new RangeError(`Index ${index} does not exist in the list.`);
            }

        }

        /*
         * If code continues to execute here, it means `current` is the node
         * to insert new data after. So `current.next` must point to
         * `newNode` for the data to be in the correct spot, but before that,
         * `newNode.next` must point to `current.next` to ensure the list
         * remains intact.
         */
        newNode.next = current.next;
        current.next.previous = newNode;

        current.next = newNode;
        newNode.previous = current;
    }
Inserts some data into the middle of the list. This method traverses
the existing list and places the data in a new item after a specific index.
@param {*} data The data to add to the list.
@param {int} index The zero-based index after which to insert the data.
@returns {void}
@throws {RangeError} If the index doesn't exist in the list.