To add onto Ben Doom's answer, I'm including some example code of a way this can be accomplished. There are multiple approaches and ways of names, organizing and calling the code below, but hopefully it is helpful.
At some point during request start, store information about the process in shared scope and return an ID to the client. Here are example functions that could be used on page or remote requests.
<cffunction name="createProcess" output="false">
<cfset var id = createUUID()>
<cfset application.processInfo[id] = {
progress = 0,
kill = false
}>
<cfreturn id />
</cffunction>
Client can then check progress by polling server, or submit request to kill process
<cffunction name="getProcessProgress" output="false">
<cfargument name="processID" required="true">
<cfreturn application.processInfo[arguments.processID].progress />
</cffunction>
<cffunction name="killProcess" output="false">
<cfargument name="processID" required="true">
<cfset application.processInfo[arguments.processID].kill = true />
</cffunction>
The actual server-side process in question can then hit a function, for example during a loop, to check whether it should abort processing and cleanup any work as appropriate.
<cffunction name="shouldKillProcess" output="false">
<cfargument name="processID" required="true">
<cfreturn application.processInfo[arguments.processID].kill />
</cffunction>