'How do I stop an async function sorting algorithm onClick with React.js and D3.js?

QUESTION: How do I terminate a running asynchronous function onClick? The execution is delayed using the await keyword.

I am making a sorting algorithm visualizer and I've ran into an issue.

First off, I've defined a height and a width parameter for CSS and D3 sizing purposes:

let h = 500;
let w = 1200;

I have several async sorting algorithms with await functionalities to slow them down for display, but for the sake of the question I will only show one along with the await function:

const sleep = (milliseconds) => {
    return new Promise(resolve => setTimeout(resolve, milliseconds))
}
async function selectionSort(inputArr) {
    let n = inputArr.length;
    let indexer = 0;
    let indexer2 = 0;
    for (let i = 0; i < n; i++) {
        let min = i;
        for (let j = i + 1; j < n; j++) {
            if (inputArr[j] < inputArr[min]) {
                min = j;
                indexer = inputArr.indexOf(inputArr[i]);
                indexer2 = inputArr.indexOf(inputArr[min]);
                await sleep(5);
                updateD3(inputArr, indexer, indexer2, h, w);
            }
        }
        if (min != i) {
            let tmp = inputArr[i];
            inputArr[i] = inputArr[min];
            inputArr[min] = tmp;
            indexer = inputArr.indexOf(inputArr[i]);
            indexer2 = inputArr.indexOf(inputArr[min]);
            await sleep(5);
            updateD3(inputArr, indexer, indexer2, h, w);
        }
    }
}

Additionally, I have an updateD3 function which I use as a callback to re-render within the SVG element upon every loop iteration when the sorting function is called:

function updateD3(arr, indexer, indexer2, h, w) {
    d3
        .selectAll('rect')
        .remove()
    const yScale = d3
        .scaleLinear()
        .domain(d3
            .extent(arr))
        .range([0, h])
    d3
        .select('#svg')
        .selectAll('rect')
        .data(arr)
        .enter()
        .append('rect')
        .attr('width', (_d, i) => {
            if (indexer == i) {
                return (w / arr.length);
            } else {
                return (0.95 * (w / arr.length));
            }
        })
        .attr('height', d => yScale(d))
        .attr('fill', 'white')
        .attr('x', (_d, i) => i * w / arr.length)
        .attr('y', d => h - yScale(d))
}

Another critical piece of the code is producing the random array, I have used the following code to do that:

function shuffleArray(array) {
    for (var i = array.length - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        var temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}
function createOrderedArray(n) {
    let array = [];
    for (let i = 0; i < n; i++) {
        array.push(i + 1);
    }
    return array;
}
let sortingHeights = createOrderedArray(1000);
shuffleArray(sortingHeights);

Then, I have a react class component under all of the above code with an SVG that renders rect elements with D3 depending on onClick functions. I have simplified and reduced it for the sake of the question like so:

class App extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            sorter: "Selection Sort"
        }
        this.play = this.play.bind(this);
        this.rdm = this.rdm.bind(this);
    }
    play() {
        selectionSort(sortingHeights);
    }
    rdm() {
        /* this is what I'm trying to figure out but we'll get to it in a second */
    }
    render() {
        return (
            <div id='app'>
                <div id="settings">
                    <h3 id="sorter-text">{this.state.sorter}</h3>
                    <div id='play-and-rdm'>
                        <button onClick={this.play} id="play">Play</button>
                        <button onClick={this.rdm} id='rdm'>Rdm</button>
                    </div>
                </div>
                <div id="svg-wrapper">
                    <svg id="svg">

                    </svg>
                </div>
            </div>
        );
    }
    componentDidMount() {
        /* this is for the initial display of the rect elements so they display randomly */
        const yScale = d3
            .scaleLinear()
            .domain(d3
                .extent(sortingHeights))
            .range([0, h])
        d3
            .select('#svg')
            .selectAll('rect')
            .data(sortingHeights)
            .enter()
            .append('rect')
            .attr('width', (_d) => (0.95 * (w / sortingHeights.length)))
            .attr('height', d => yScale(d))
            .attr('fill', this.state.colorBars)
            .attr('x', (_d, i) => i * w / sortingHeights.length)
            .attr('y', d => h - yScale(d))
    }
}

You'll notice in the class component I bound a method called rdm(). When I click the Rdm button, I want the method to abort/kill the async sorting algorithm if it's still running, and randomize the D3 rect elements after. This is so the algorithm does not continue to run after randomization (the bug). I have no idea how since I am not yet completely familiar with async functionalities, or if it's even possible at all. What I had in mind is something along the lines of:

rdm() {
/* kill the async function */
    selectionSort().kill();
/* randomize the array */
    sortingHeights = shuffleArray(sortingHeights);
/* re-render rect heights with randomized values from array */
    updateD3(sortingHeights, undefined, undefined, h, w)
}

I want to find a way to make the async sorting algorithm stop running (some kind of kill or abort) onClick so I can randomize the array and re-render the D3 using it without the algorithm continuing the sort at it's current index, or breaking the program entirely. Thank you for any solutions or suggestions.

I am doing all of this from the browser with Babel Standalone, React Dev CDN, and D3 CDN.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source