Dependent async tasks executor in JavaScript

Consider we have multiple async tasks A, B, C, D, and E ( not promises). A, B, and C are independent tasks while D depends on A and B to perform its task while E depends on D and C to perform its task. Write a task function/class in JavaScript to solve this problem.

To solve this problem. We create a class that will take the dependencies and a callback function as input.

In the dependencies list, we will check if all the tasks in the list is completed or not, if it is completed then filter them out as we no longer need to run them.

If there are no items in dependencies, invoke the callback directly.

Otherwise, if there are dependencies pending, push them into a list, and execute them one by one. Once all are completed invoke the callback.

Use a flag to determine the state of the task, i.e completed or not.

class Task {
    // accept the dependencies list
    // and the callback
    constructor(dependencies = [], job) {
        // filter the dependencies that are not yet completed
        this.dependencies = dependencies ? dependencies.filter(dependency => dependency instanceof Task && !dependency.isCompleted) : [];
        this.currentDependencyCount = this.dependencies.length;
      
        // the callback
        this.job = job;
      
        // if current task is done
        this.isCompleted = false;
        
        // store the dependencies list callback
        // to execute is sequence
        this.subscribedList = [];
        
        // start the job
        this.processJob();
    }
    
    
    processJob() {
        // if there is dependency
        // subsribe to each of them
        if (this.dependencies && this.dependencies.length) {
            for (let dependency of this.dependencies) {
                dependency.subscribe(this.trackDependency.bind(this));
            }
        } 
        // else invoke the callback directly
        else {
            this.job(this.done.bind(this));
        }
    }
    
    // if all the dependecies are excuted
    // invoke the callback
    trackDependency() {
        this.currentDependencyCount--;
        if (this.currentDependencyCount === 0) {
            this.job(this.done.bind(this));
        }
    }
   
    // push the callback to the list
    subscribe(cb) {
        this.subscribedList.push(cb);
    }
  
    // if the current task is done
    // mark it as complete
    // invoke all the dependecy callbacks.
    // to print it in sequence
    done() {
        this.isCompleted = true;
        for (const callback of this.subscribedList) {
            callback();
        }
    }
}
Input:
const processA = new Task(null, (done) => {
    setTimeout(() => {
        console.log('Process A');
        done();
    }, 100);
});

const processB = new Task([processA], (done) => {
    setTimeout(() => {
        console.log('Process B');
        done();
    }, 1500);
});

const processC = new Task(null, (done) => {
    setTimeout(() => {
        console.log('Process C');
        done();
    }, 1000);
});

const processD = new Task([processA, processB], (done) => {
    setTimeout(() => {
        console.log('Process D');
        done();
    }, 1000);
});

const processE = new Task([processC, processD], (done) => {
    setTimeout(() => {
        console.log('Process E');
        done();
    }, 100);
});

const createAllDoneInstance = (allDoneCallback) => new Task([processA, processB, processC, processD, processE], allDoneCallback);

createAllDoneInstance((done) => {
    console.log('All is done!');
    done();
});

Output:
"Process A"
"Process C"
"Process B"
"Process D"
"Process E"
"All is done!"