Search code examples
javascriptpromisees6-promise

How to dismiss a loading overlay when all promises are settled when promises are continuously added


I have the following class that I'm using to route all my API requests through

class PromiseBroker {
    #promises = {};
    #overlayEle = document.getElementById('loading-overlay');
    constructor(){

    }
    get promises(){
        return this.#promises;
    }
    setPromise(key, value){
        this.promises[key] = value;
        this.#overlayEle.classList.remove('d-none');
        Promise.allSettled(Object.values(this.promises)).then(results => {
            this.#overlayEle.classList.add('d-none')
        });
    }
}

The purpose of this class is to dismiss the loading overlay once all API requests are finished. I don't know how many requests will be made ahead of time. The issue I'm having is that as promises are added, it will be dismissed prematurely. Here is what I'm referring to:

  1. Promise A is added - loading overlay is added
  2. Promise A resolves - loading overlay is dismissed
  3. Promise B is added - loading overlay is added
  4. Promise C is added
  5. Promise B resolves - loading overlay is dismissed
  6. Promise C resolves - here is when I want the overlay to be dismissed

I would want to dismiss only when all promises (including ones that have been added since the allSettled call) have been resolved. The issue, I'm assuming, is that the state changes while awaiting the promises to settle. I don't know if there is a way to cancel or overwrite the allSettled to only use the most recent call.


Solution

  • Don't use allSettled at all. Watch the completion of each individual promise only once, and keep a collection of (keys for) promises that are pending:

    const defaultUpdate = activeCount => {
        const overlay = document.getElementById('loading-overlay');.
        overlay.classList.toggle('d-none', activeCount == 0);
    };
    
    class PromiseBroker {
        #promises = {};
        #active = new Set();
        #update;
        constructor(update = defaultUpdate) {
             this.#update = update;
        }
        get promises(){
            return this.#promises;
        }
        get activeCount() {
           return this.#active.size;
        }
        setPromise(key, value) {
            this.promises[key] = value;
            this.#active.add(key);
            this.#update(this.activeCount);
            const done = () => {
                this.#active.delete(key);
                this.#update(this.activeCount);
            };
            Promise.resolve(value).then(done, done);
        }
    }