javascriptnode.jspromisebluebird

JavaScript Promise Dependency Handling


I have the following code example.

var Promise = require('bluebird');

var model = function (object) {
    this.name = object.name;
};

model.prototype.download = function () {
    var self = this;
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve();
        }, Math.random() * 100)
    });
};

model.prototype.process = function () {
    var self = this;
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log('processed: ', self.name);
            resolve();
        }, Math.random() * 100)
    });
};



var models = [new model({
    name: 'user',
    requires: ['company']
}), new model({
    name: 'address',
    requires: ['user', 'company']
}), new model({
    name: 'company'
})];

Promise.map(models, function (model) {
    return model.download()
        .then(function () {
            return model.process();
        });
});

The required output of this code is:

processed: company // 1rst, because company model has no dependencies
processed: user // 2nd, because user requires company
processed: address // 3rd, because address requires company and user

I need to manage somehow the dependencies. The model.process function should be triggered only when all the process functions of the model's required models have already been resolved.

It's just a small example, I have a lot of models with multiple dependencies.

I need to trigger the download functions synchronously, and trigger the process function as soon as possible. I can not wait all the downloads to be resolved and call process after.


Solution

  • This is an example how you can asynchronously traverse a directed acyclic graph, without evaluating the individual nodes multiple times. Be careful, cycles in the dependency graph cause a deadlock in this implementation.

    function Model(name, requires) {
      this.name = name;
      this.requires = requires;
    };
    
    
    // this function is available as `Promise.delay` when using bluebird
    function delay(x, v) {
      return new Promise(resolve => {
        setTimeout(() => { resolve(v); }, x);
      });
    }
    
    Model.prototype.process = function () {
      console.log('started processing: ', this.name);
      return delay(Math.random() * 100 + 100).then(() => {
        console.log('finished processing: ', this.name);
      });
    };
    
    function Processor(models) {
      this.processMap = {};
      this.models = models;
      
      models.forEach(m => {
        this.processMap[m.name] = {
          promise: null,
          model: m
        };
      });
    }
    
    Processor.prototype.processDependencies = function(model) {
      return Promise.all(model.requires.map(r => this.processByName(r)));
    };
    
    Processor.prototype.process = function(model) {
      const process = this.processMap[model.name];
      if (!process.promise) {
        process.promise = this.processDependencies(model)
          .then(() => model.process());
      }
      return process.promise;
    };
    
    Processor.prototype.processByName = function(modelName) {
      return this.process(this.processMap[modelName].model);
    };
    
    
    function test() {
      const models = [
        new Model('bottom', []),
        new Model('mid a', ['bottom']),
        new Model('mid b', ['bottom']),
        new Model('top', ['mid a', 'mid b'])
      ];
      
      const processor = new Processor(models);
    
      Promise.all(
        models.map(m => processor.process(m))
      ).then(allResults => {
        console.log("All process finished");
      }, e => {
        console.error(e);
      });
    }
    
    test();