From 86196c3feba1133598bf6706ca70d773ba1ab329 Mon Sep 17 00:00:00 2001 From: ztimson Date: Sat, 27 Apr 2024 23:09:12 -0400 Subject: [PATCH] Added promise progress --- package.json | 2 +- src/download.ts | 45 ----------------------------------------- src/index.ts | 2 +- src/promise-progress.ts | 26 ++++++++++++++++++++++++ src/upload.ts | 30 --------------------------- src/xhr.ts | 1 - 6 files changed, 28 insertions(+), 78 deletions(-) create mode 100644 src/promise-progress.ts delete mode 100644 src/upload.ts diff --git a/package.json b/package.json index 2dda74a..c45b50e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ztimson/utils", - "version": "0.8.2", + "version": "0.9.0", "description": "Utility library", "author": "Zak Timson", "license": "MIT", diff --git a/src/download.ts b/src/download.ts index 40291ac..907d4ea 100644 --- a/src/download.ts +++ b/src/download.ts @@ -1,11 +1,3 @@ -import {TypedEmitter, TypedEvents} from './emitter'; - -export type DownloadEvents = TypedEvents & { - complete: (blob: Blob) => any; - failed: (error: Error) => any; - progress: (progress: number) => any; -} - export function download(href: any, name: string) { const a = document.createElement('a'); a.href = href; @@ -14,40 +6,3 @@ export function download(href: any, name: string) { a.click(); document.body.removeChild(a); } - -/** - * Download a URL using fetch so progress can be tracked. Uses Typed Emitter to emit a "progress" & - * "complete" event. - * - * @param {string} url - * @param {string} downloadName - * @return {TypedEmitter} - */ -export function downloadProgress(url: string, downloadName?: string) { - const progress = new TypedEmitter(); - fetch(url).then(response => { - if(!response.ok) return progress.emit('failed', new Error(response.statusText)); - const contentLength = response.headers.get('Content-Length') || '0'; - const total = parseInt(contentLength, 10); - let chunks: any[] = [], loaded = 0; - const reader = response.body?.getReader(); - reader?.read().then(function processResult(result) { - if(result.done) { - const blob = new Blob(chunks); - if(downloadName) { - const url = URL.createObjectURL(blob); - download(url, downloadName); - URL.revokeObjectURL(url); - } - progress.emit('complete', blob); - } else { - const chunk = result.value; - chunks.push(chunk); - loaded += chunk.length; - progress.emit('progress', loaded / total); - reader.read().then(processResult); - } - }); - }).catch(err => progress.emit('failed', err)); - return progress; -} diff --git a/src/index.ts b/src/index.ts index e3bcb80..a705372 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ export * from './errors'; export * from './logger'; export * from './misc'; export * from './objects'; +export * from './promise-progress'; export * from './string'; export * from './time'; -export * from './upload'; export * from './xhr'; diff --git a/src/promise-progress.ts b/src/promise-progress.ts new file mode 100644 index 0000000..d3a075a --- /dev/null +++ b/src/promise-progress.ts @@ -0,0 +1,26 @@ +export type ProgressCallback = (progress: number) => any; + +export class PromiseProgress extends Promise { + private listeners: ProgressCallback[] = []; + + private _progress = 0; + get progress() { return this._progress; } + set progress(p: number) { + if(p == this._progress) return; + this._progress = p; + this.listeners.forEach(l => l(p)); + } + + constructor(executor: (resolve: (value: T) => any, reject: (reason: any) => void, progress: (progress: number) => any) => void) { + super((resolve, reject) => executor( + (value: T) => resolve(value), + (reason: any) => reject(reason), + (progress: number) => this.progress = progress + )); + } + + onProgress(callback: ProgressCallback) { + this.listeners.push(callback); + return this; + } +} diff --git a/src/upload.ts b/src/upload.ts deleted file mode 100644 index 30dbc40..0000000 --- a/src/upload.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {TypedEmitter, TypedEvents} from './emitter'; - -export type UploadEvents = TypedEvents & { - complete: () => any; - failed: (err: Error) => any; - progress: (progress: number) => any; -} - -export function uploadProgress(files : File | File[], url: string) { - const xhr = new XMLHttpRequest(); - const progress = new TypedEmitter(); - const formData = new FormData(); - (Array.isArray(files) ? files : [files]) - .forEach(f => formData.append('file', f)); - - xhr.upload.addEventListener("progress", (event) => { - if(event.lengthComputable) progress.emit('progress', event.loaded / event.total); - }); - - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if(xhr.status >= 200 && xhr.status < 300) progress.emit('complete'); - else progress.emit('failed', new Error(xhr.responseText)); - } - }; - - xhr.open("POST", url, true); - xhr.send(formData); - return progress; -} diff --git a/src/xhr.ts b/src/xhr.ts index 7fa4132..68104fd 100644 --- a/src/xhr.ts +++ b/src/xhr.ts @@ -1,4 +1,3 @@ -import {TypedEmitter, TypedEvents} from './emitter.ts'; import {clean} from './objects'; export type Interceptor = (request: Response, next: () => void) => void;