Progress
Overview
If you're writing a file downloader or task runner, you may want to display current progress to the end user. Progress helps you create a promise with progress reporting and listening / inspection.
Class
class Progress<Result = unknown, CurrentProgress = unknown> extends Future<Result>
Where:
Result
: type of its fulfilled value, defaults to unknown
CurrentProgress
: type of progress report, defaults to unknown
Future<Result>
: Progress inherits all methods and properties from Future
Properties
progress
get progress(): CurrentProgress
Gets the current progress (last reported, despite current progress
state)
Methods
constructor
constructor(initialProgress: CurrentProgress)
Constructs a new Progress object with given initial progress.
run
static run<Result = unknown, CurrentProgress = unknown>(
fn: (progress: Progress<Result, CurrentProgress>) => unknown,
initialProgress: CurrentProgress
): Progress<Result, CurrentProgress>
A static helper method to create a new progress object, runs the given function with the progress as parameter, and returns the created progress object.
The function should report progress and call progress.resolve
/ progress.reject
once done.
report
report(progress: CurrentProgress)
report current progress and notify all listeners, will have no effect if progress has already fulfilled or rejected.
onProgress
onProgress(listener: (progress: Readonly<CurrentProgress>) => unknown)
register a listener on progress report, and use the returned function to cancel listening.
inspect
inspect(): ProgressInspectionResult<Result, CurrentProgress>
type ProgressInspectionResult<Result, Progress> =
| {
state: 'pending'
progress: Progress
}
| {
state: 'fulfilled'
value: Result
}
| {
state: 'rejected'
reason: unknown
}
Inspects the progress, useful when debugging and it should only be used in debugging senarios.
Examples
resolve on all tasks finished
const progress = new Progress<string, { current: number; total: number }>({ current: 0, total: 100 })
// automatically resolve when all tasks are finished
progress.onProgress((p) => p.current >= p.total && progress.resolve('banana!'))
progress.report({ current: 15, total: 100 })
progress.report({ current: 100, total: 100 })
expect(await progress).toBe('banana!')
download progress with browser fetch
const download => async (url) => {
return Progress.run<number, number>((progress) => {
const response = await fetch(url)
const reader = response.body.getReader()
// you may use 'Content-Length' header to detect total bytes to download and caculate a percentage
// if the server supports
// read chunks
let received = 0
let chunks = []
while(true) {
const { done, value } = await reader.read()
if (done) break
chunks.push(value)
received += value.length
progress.report(received)
}
// concatenate all chunks
let data = new Uint8Array(received)
let position = 0
for(const chunk of chunks) {
data.set(chunk, position)
position += chunk.length
}
progress.resolve(new TextDecoder('utf-8').decode(data))
}
}
assuming you're using React
const DownloadProgress: React.FC<{ progress: Progress }> = ({ progress }) => {
const [downloaded, setDownloaded] = useState(0)
useEffect(() => {
return progress.onProgress(setDownloaded)
})
return <p>{downloaded / (1024 * 1024 * 1024)} MiB downloaded.</p>
}