Prerequisite

Module start might be asycnchronous

Each module has an initing phase, and the phase might be asynchoronous because you want to do something asynchronously. For example, a module may need to read some configuration from db to set up the environment; we will say this module is not ready yet if it's still in the init phase.

Service is playing the role of message queuing between modules

Servicejs
https://github.com/alivedise/servicejs

We call this 'Durable subscriber' in the messaging pattern.
That is, event dispatching will be lost if the subscriber is not ready when the dispatcher loads before the subscriber. To avoid this, we need a third module to queue the request of the dispatcher before the subscriber is able to register to the requests.

How to know the application is fully loaded if we decide to delay something?

A rough idea here is let every module to notify the centralized controller when it starts.

var A = {
    start: function() {
    Service.request('notifyStarted');
  };
};
A.start();

But this does not make sense because we need to maintain a list of all the modules in the centralized controller.

Using Promise

var A = {
    start: function() {
    return this.asyncOperationWhoWillReturnPromise();
  }
};
A.start().then(() => {
    console.log('A is done');
});

A promise chain of start operations

A module may have some sub modules to load and start in the init phase. If we connect all the start function using Promise, we could have a simple start function call from the application root and simply knows when all the sub modules are started.

var App = {
    start: function() {
    return lazyLoadModule(['A', 'B', 'C']).then(() => {
      return Promise.all([
        this.subModuleA.start(),
        this.subModuleB.start(),
        this.subModuleC.start()
      ]);
    });
  }
};
App.start().then(() => {
    console.log('Cool, everything is done.');
});

Tricky: Define critical modules and side modules but keep the start promise

For reason of performance we don't want everything to be loaded together in startup. That is to say, we need to figure out what's the critical module and what's the side modules.

var App = {
    start: function() {
    return lazyLoadModule(['A', 'B', 'C']).then(() => {
      return Promise.all([
        this.subModuleA.start(),
        this.subModuleB.start(),
        this.subModuleC.start(),
        this.loadWhenIdle('D', 'E', 'F');
      ]);
    });
  },
  loadWhenIdle: function(modules) {
    return Service.request('schedule').then(() => {
        return lazyLoadModule(modules);
    }).then(() => {
        return modules.map(function(module) {
        return module.start();
      }, this);
    });
  }
};
App.start().then(() => {
    console.log('Cool, everything is done.');
});

The loadWhenIdle function call will not being resolved once the consumer of 'schedule' Service tell it now is time to do your operation. So now we need a new module to schedule the non critical operations. It should know the timing that all critical modules are loaded and started, and then execute the queued requests.

var CriticalLeafModule = {
  start: function() {
    // I am the last important module in the tree

    Service.request('notifyVisuallyLoaded');
  }
};
var Scheduler = {
    start: function() {
    Service.register('notifyVisuallyLoaded', this);
    Service.register('schedule', this);
  },
  schedule: function() {
    return new Promise(function(resolve) {
      if (!this.visuallyLoaded) {
        this.queuedTasks.push({
            resolve: resolve
        });
      } else {
        resolve();
      }
    });
  },
  notifyVisuallyLoaded: function() {
    this.visuallyLoaded = true;
    this.queuedTasks.forEach(function(task) {
        task.resolve();
    });
    this.queuedTasks = [];
  }
};

Real case: System startup timeline

Comments

comments powered by Disqus