Skip to content

Commit

Permalink
feat: impl boot methods (#171)
Browse files Browse the repository at this point in the history
1. Add `Lifecycle` class, control EggCore lifecycle.
2. After config, extend, app.js, angent.js loaded, loader should call `initBoots`.
3. After all files loaded, loader should call `triggerDidLoad`.

Refs: eggjs/egg#2520
  • Loading branch information
killagu authored and popomore committed Sep 6, 2018
1 parent ae38fa4 commit 9d2f2fc
Show file tree
Hide file tree
Showing 31 changed files with 1,150 additions and 216 deletions.
157 changes: 58 additions & 99 deletions lib/egg.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@ const BaseContextClass = require('./utils/base_context_class');
const utils = require('./utils');
const Router = require('./utils/router');
const Timing = require('./utils/timing');
const Lifecycle = require('./lifecycle');

const DEPRECATE = Symbol('EggCore#deprecate');
const CLOSESET = Symbol('EggCore#closeSet');
const ISCLOSE = Symbol('EggCore#isClose');
const CLOSE_PROMISE = Symbol('EggCore#closePromise');
const ROUTER = Symbol('EggCore#router');
const EGG_LOADER = Symbol.for('egg#loader');
const INIT_READY = Symbol('EggCore#initReady');

const CLOSE_PROMISE = Symbol('EggCore#closePromise');

class EggCore extends KoaApplication {

Expand All @@ -44,16 +41,9 @@ class EggCore extends KoaApplication {

this.timing = new Timing();

// register a close set
this[CLOSESET] = new Set();
// cache deprecate object by file
this[DEPRECATE] = new Map();

this[INIT_READY]();

this.timing.start('Application Start');
this.ready(() => this.timing.end('Application Start'));

/**
* @member {Object} EggCore#options
* @private
Expand Down Expand Up @@ -108,6 +98,15 @@ class EggCore extends KoaApplication {
*/
this.Service = Service;

this.lifecycle = new Lifecycle({
baseDir: options.baseDir,
app: this,
logger: this.console,
});
this.lifecycle.on('error', err => this.emit('error', err));
this.lifecycle.on('ready_timeout', id => this.emit('ready_timeout', id));
this.lifecycle.on('ready_stat', data => this.emit('ready_stat', data));

/**
* The loader instance, the default class is {@link EggLoader}.
* If you want define
Expand Down Expand Up @@ -207,111 +206,71 @@ class EggCore extends KoaApplication {
* @param {Function|GeneratorFunction|AsyncFunction} scope function will execute before app start
*/
beforeStart(scope) {
if (!is.function(scope)) {
throw new Error('beforeStart only support function');
}

// get filename from stack
const name = utils.getCalleeFromStack(true);
const timingkey = 'Before Start in ' + utils.getResolvedFilename(name, this.options.baseDir);

this.timing.start(timingkey);

const done = this.readyCallback(name);
this.lifecycle.registerBeforeStart(scope);
}

// ensure scope executes after load completed
process.nextTick(() => {
utils.callFn(scope).then(() => {
done();
this.timing.end(timingkey);
}, err => {
done(err);
this.timing.end(timingkey);
});
});
/**
* register an callback function that will be invoked when application is ready.
* @see https://github.com/node-modules/ready
* @since 1.0.0
* @param {boolean|Error|Function} flagOrFunction -
* @return {Promise|null} return promise when argument is undefined
* @example
* const app = new Application(...);
* app.ready(err => {
* if (err) throw err;
* console.log('done');
* });
*/
ready(flagOrFunction) {
return this.lifecycle.ready(flagOrFunction);
}

/**
* Close all, it will close
* - callbacks registered by beforeClose
* - emit `close` event
* - remove add listeners
* If a client starts asynchronously, you can register `readyCallback`,
* then the application will wait for the callback to ready
*
* If error is thrown when it's closing, the promise will reject.
* It will also reject after following call.
* @return {Promise} promise
* It will log when the callback is not invoked after 10s
*
* Recommend to use {@link EggCore#beforeStart}
* @since 1.0.0
*
* @param {String} name - readyCallback task name
* @param {object} opts -
* - {Number} [timeout=10000] - emit `ready_timeout` when it doesn't finish but reach the timeout
* - {Boolean} [isWeakDep=false] - whether it's a weak dependency
* @return {Function} - a callback
* @example
* const done = app.readyCallback('mysql');
* mysql.ready(done);
*/
close() {
if (this[CLOSE_PROMISE]) return this[CLOSE_PROMISE];

const closeFunction = async () => {
// close in reverse order: first created, last closed
const closeFns = Array.from(this[CLOSESET]);
for (const fn of closeFns.reverse()) {
await utils.callFn(fn);
this[CLOSESET].delete(fn);
}
// Be called after other close callbacks
this.emit('close');
this.removeAllListeners();
this[ISCLOSE] = true;
};
this[CLOSE_PROMISE] = closeFunction();
return this[CLOSE_PROMISE];
readyCallback(name, opts) {
return this.lifecycle.legacyReadyCallback(name, opts);
}

/**
* Register a function that will be called when app close
* @param {Function} fn - the function that can be generator function or async function
*/
beforeClose(fn) {
assert(is.function(fn), 'argument should be function');
this[CLOSESET].add(fn);
this.lifecycle.registerBeforeClose(fn);
}

/**
* @member {Function}
* @private
* Close all, it will close
* - callbacks registered by beforeClose
* - emit `close` event
* - remove add listeners
*
* If error is thrown when it's closing, the promise will reject.
* It will also reject after following call.
* @return {Promise} promise
* @since 1.0.0
*/
[INIT_READY]() {
/**
* register an callback function that will be invoked when application is ready.
* @method {Function} EggCore#ready
* @see https://github.com/node-modules/ready
* @since 1.0.0
* @example
* const app = new Application(...);
* app.ready(err => {
* if (err) throw err;
* console.log('done');
* });
*/

// get app timeout from env or use default timeout 10 second
const eggReadyTimeoutEnv = Number.parseInt(process.env.EGG_READY_TIMEOUT_ENV || 10000);
assert(Number.isInteger(eggReadyTimeoutEnv), `process.env.EGG_READY_TIMEOUT_ENV ${process.env.EGG_READY_TIMEOUT_ENV} should be able to parseInt.`);

/**
* If a client starts asynchronously, you can register `readyCallback`,
* then the application will wait for the callback to ready
*
* It will log when the callback is not invoked after 10s
*
* Recommend to use {@link EggCore#beforeStart}
* @method {Function} EggCore#readyCallback
* @since 1.0.0
* @example
* const done = app.readyCallback('mysql');
* mysql.ready(done);
*/
require('ready-callback')({ timeout: eggReadyTimeoutEnv }).mixin(this);

this.on('ready_stat', data => {
this.console.info('[egg:core:ready_stat] end ready task %s, remain %j', data.id, data.remain);
}).on('ready_timeout', id => {
this.console.warn('[egg:core:ready_timeout] %s seconds later %s was still unable to finish.', eggReadyTimeoutEnv / 1000, id);
});
async close() {
if (this[CLOSE_PROMISE]) return this[CLOSE_PROMISE];
this[CLOSE_PROMISE] = this.lifecycle.close();
return this[CLOSE_PROMISE];
}

/**
Expand Down
Loading

0 comments on commit 9d2f2fc

Please sign in to comment.