UPDATE: See strutzky's answer (above) and the comments for at least one example where this does not behave as I expect and describe here. I will have to experiment/read further to update my understanding when time permits...
If your caller interacts with the database asynchronously or is threaded/multi-process, so you can open a second session while the first is still running, you could create a table to hold the partial data and update that as the procedure progresses. This can then be read by a second session with the transaction isolation level1 set to enable it to read uncommitted changes:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table
1: as per the comments and subsequent update in srutzky's answer, setting the isolation level is not required if the process being monitored is not wrapped in a transaction, though I tend to set it out of habit in such circumstances as it doesn't cause harm when not needed in these cases
Of course if you could have multiple processes operating this way (which is likely if your web server accepts concurrent users and it is very rare for that not to be the case) you'll need to identify the progress information for this process in some way. Perhaps pass the procedure a freshly minted UUID as a key, add that to the progress table, and read with:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM progress_table WHERE process = <current_process_uuid>
I've used this method to monitor long running manual processes in SSMS. I can't decide whether it "smells" too much for me to consider using it in production though...