Search code examples
typescriptweb-audio-apies6-promise

Promise issues in typescript


I've been experimenting Web Audio API with typescript. I'm in the process of creating an AudioDownloader class that downloads the audio data for a single or multiple audio files and returns a single or array of promises. In the case of downloading multiple audio files I don't want the Promise.all to get failed because of a single failure so I used a method called reflect (which I borrowed from net).

For some reason, the typescript compilation fails in the download method due to mismatch in return type. I've been trying to sort this out for last two days with no luck. I really don't understand what mistake I'm doing here. Can someone please help?

class PromiseResult<T> {
    value: T;
    error?: any;
    status: PromiseStatus;
}

enum PromiseStatus {
    Resolved,
    Rejected
}

function reflect<T>(promise: Promise<T>): Promise<PromiseResult<T>> {
    return promise.then(v => {
        return { status: PromiseStatus.Resolved, value: v };
    }, e => {
        return { status: PromiseStatus.Rejected, error: e }
    });
}

type AudioDownloadResult = PromiseResult<AudioBuffer>;

class AudioDownloader {

    private readonly context: AudioContext;

    constructor(context: AudioContext) {
        this.context = context;
    }

    download(urls: string | Array<string>): Promise<AudioDownloadResult | Array<AudioDownloadResult>> {
        if (typeof urls === 'string') {
            return reflect(this.downloadOne(urls as string));
        }

        return Promise.all((<Array<string>>urls).map(this.downloadOne).map(reflect));
    }

    private downloadOne(url: string): Promise<AudioDownloadResult> {
        return new Promise((resolve, reject) => {
            const req = new XMLHttpRequest();
            req.open('GET', url, true);
            req.responseType = 'arraybuffer';
            req.addEventListener('load', () => {
                this.context.decodeAudioData(req.response).then(buffer => {
                    resolve(buffer);
                }, reject);
            }, false);
            req.addEventListener('error', reject, false);
            req.send();
        });
    }
}

Solution

  • I would refactor functions a bit and get it to work as expected by doing so:

    class PromiseResult<T> {
        value: T;
        error?: any;
        status: PromiseStatus;
    }
    
    enum PromiseStatus {
        Resolved,
        Rejected
    }
    
    type AudioDownloadResult = PromiseResult<AudioBuffer>;
    
    class AudioDownloader {
    
        private readonly context: AudioContext;
    
        constructor(context: AudioContext) {
            this.context = context;
        }
    
        async download(urls: string | Array<string>): Promise<AudioDownloadResult | Array<AudioDownloadResult>>
        {
          try
          {
            if (typeof urls === 'string')
            {
                return await this.downloadOne(urls as string);
            }
    
            let arr: AudioDownloadResult[] = [];
            for (let url of urls)
            {
              arr.push(await this.downloadOne(url));
            }
    
            return arr;
          }
          catch (err)
          {
            return { status: PromiseStatus.Rejected, error: err } as AudioDownloadResult;
          }
        }
    
        private downloadOne(url: string): Promise<AudioDownloadResult> {
            return new Promise((resolve, reject) => {
                const req = new XMLHttpRequest();
                req.open('GET', url, true);
                req.responseType = 'arraybuffer';
                req.addEventListener('load', () => {
                    this.context.decodeAudioData(req.response).then(buffer => {
                        resolve(buffer);
                    }, reject);
                }, false);
                req.addEventListener('error', reject, false);
                req.send();
            });
        }
    }
    

    I have not tested it - but this should do the job.