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.

Code Mala Tang
Code Mala Tang
Code Mala Tang
How Egg.js Core Manages Lifecycle with get-ready and ready-callback

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-ready

provides 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 logic

What is ready-callback

ready-callback

builds 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}\\\
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.