How Egg.js Core Manages Lifecycle with get-ready and ready-callback
This article explains Egg.js's core module, detailing its lifecycle implementation, the ILifecycleBoot interface, and how the get-ready and ready-callback packages work together to coordinate asynchronous initialization, plugin loading, and server start‑up in a clean, extensible way.
About Egg
Egg.js was first released on 2017‑03‑21 and, despite limited recent updates, remains widely used in China for its MVC layered architecture. While marketed for enterprise‑level projects, its convention‑based design can lead to tangled dependencies in large codebases.
Egg‑Core
Egg‑core is the foundational module of Egg.js, handling framework startup, plugin loading, and the application lifecycle.
Lifecycle Interface
export interface ILifecycleBoot {
configWillLoad?(): void;
configDidLoad?(): void;
didLoad?(): Promise<void>;
willReady?(): Promise<void>;
didReady?(err?: Error): Promise<void>;
serverDidReady?(): Promise<void>;
beforeClose?(): Promise<void>;
}The implementation relies on two packages: get-ready and ready-callback. ready-callback is a second‑level wrapper around get-ready.
What is get-ready
get-readyprovides a one‑time ready event useful for scenarios such as fetching configuration data before the application starts.
export class Ready {
#isReady: boolean;
#readyCallbacks: CallbackFunction[];
#readyArg?: Error = undefined;
constructor() {
this.#isReady = false;
this.#readyCallbacks = [];
}
ready(flagOrFunction?: ReadyFunctionArg) {
if (flagOrFunction === undefined || typeof flagOrFunction === 'function') {
return this.#register(flagOrFunction);
}
this.#emit(flagOrFunction);
}
#register(func?: CallbackFunction) {
if (!func) {
return new Promise<void>((resolve, reject) => {
function func(err?: Error) {
if (err) reject(err);
else resolve();
}
if (this.#isReady) {
return func(this.#readyArg);
}
this.#readyCallbacks.push(func);
});
}
if (this.#isReady) {
func(this.#readyArg);
} else {
this.#readyCallbacks.push(func);
}
}
#emit(flag: boolean | Error) {
this.#isReady = flag !== false;
this.#readyArg = flag instanceof Error ? flag : undefined;
if (this.#isReady) {
this.#readyCallbacks
.splice(0, Infinity)
.forEach(callback => process.nextTick(() => callback(this.#readyArg)));
}
}
static mixin(obj?: any) {
if (!obj) return;
const ready = new Ready();
obj.ready = (flagOrFunction: any) => ready.ready(flagOrFunction);
}
}The class manages asynchronous readiness, allowing registration of callbacks, immediate execution if already ready, and a static mixin to inject the ready method into other objects.
Using get-ready
import { Ready } from 'get-ready';
const obj = new Ready();
obj.ready(() => console.log('ready'));
obj.ready(true);The ready method can be called without arguments to return a promise, similar to Vue’s $nextTick.
await obj.ready();
// continue with business logicWhat is ready-callback
ready-callbackbuilds on get-ready by adding timeout, lazy start, and a counting mechanism for multiple asynchronous tasks.
import EventEmitter from 'node:events';
import { debuglog } from 'node:util';
import { randomUUID } from 'node:crypto';
import once from 'once';
import { Ready as ReadyObject, type ReadyFunctionArg } from 'get-ready';
const debug = debuglog('ready-callback');
const defaults = { timeout: 10000, isWeakDep: false };
class Ready extends EventEmitter {
isError = false;
cache = new Map();
opt: ReadyOption;
obj: any;
ready: (flagOrFunction?: ReadyFunctionArg) => void;
constructor(opt: ReadyOption = {}) {
super();
ReadyObject.mixin(this);
this.opt = opt;
if (!this.opt.lazyStart) this.start();
}
start() {
setImmediate(() => {
if (this.cache.size === 0) {
debug('Fire callback directly');
this.ready(true);
}
});
}
mixin(obj?: any) {
if (!obj || this.obj) return null;
obj.ready = this.ready.bind(this);
obj.readyCallback = this.readyCallback.bind(this);
this.once('error', err => obj.ready(err));
if (obj.emit) {
this.on('ready_timeout', obj.emit.bind(obj, 'ready_timeout'));
this.on('ready_stat', obj.emit.bind(obj, 'ready_stat'));
this.on('error', obj.emit.bind(obj, 'error'));
}
this.obj = obj;
return this;
}
readyCallback(name: string, opt: ReadyCallbackOption = {}) {
opt = Object.assign({}, defaults, this.opt, opt);
const cacheKey = randomUUID();
opt.name = name || cacheKey;
const timer = setTimeout(() => this.emit('ready_timeout', opt.name), opt.timeout);
const cb = once((err?: any) => {
if (err != null && !(err instanceof Error)) err = new Error(err);
clearTimeout(timer);
if (this.isError === true) return;
setImmediate(() => this.readyDone(cacheKey, opt, err));
}) as unknown as ReadyCallbackFn;
cb.id = opt.name;
this.cache.set(cacheKey, cb);
return cb;
}
readyDone(id: string, opt: ReadyCallbackOption, err?: Error) {
if (err != null && !opt.isWeakDep) {
this.isError = true;
debug(`[${id}] Throw error task id \\`${opt.name}\\\Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Mala Tang
Read source code together, write articles together, and enjoy spicy hot pot together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
