• egg的生命周期是怎样触发的?


      以前在对egg进行换壳tcp时,看到加载了各种配置,到触发生命周期方法。然而改完过了几个月就忘记了,还是记录一下吧,以免下次忘记的时候翻翻。

       egg在load了plugin、config、extend后,最后会有一个loadCustomAgent(loadCustomApp)方法,很关键的一个方法,主要是用来启动app.js里的configWillLoad、configDidLoad、willReady等一系列生命周期方法。

      以Agent为例,在初始化Agent实例的时候,会实例化一个Lifecycle实例,Lifecycle又实例化两个Ready实例:

    get-ready就像一个搅局者,混进了各个类里面,为类注入ready方法。还有ready-callback实属是对get-ready的封装(这肯定是阿里的人写的。一看npm,果然!)

    前期准备做好后,开始:

    // egg-core/lib/loader/mixin/custom.js
    
    loadCustomAgent() {
        this[LOAD_BOOT_HOOK]('agent');
        this.lifecycle.triggerConfigWillLoad();
      },

    this[LOAD_BOOT_HOOK]在各个加载路径下找到agent.js,并把导出对象加在lifecycle的 this[BOOT_HOOKS] = []里,伪代码:

    const agentObj = require('xxx/agent.js');
    lifecycle.boot_hooks.push(agentObj);
    
    
    // 假设agent.js如以下内容
    'use strict';
    class AppBootHook {
      constructor(app) {
        this.app = app;
      }
      configWillLoad() {}
    
      async didLoad() {}
    
      async willReady() {}
    
      async didReady() {}
    
      async serverDidReady() {}
    }
    module.exports = AppBootHook;

    push进来之后,再调用lifecycle.init(),新建一个个agent实例,保存在lifecycle.boots数组:

    // egg-core/lib/lifecycle.js
    init() {
        assert(this[INIT] === false, 'lifecycle have been init');
        this[INIT] = true;
        this[BOOTS] = this[BOOT_HOOKS].map(t => new t(this.app));
      }

    最后,终于触发 this.lifecycle.triggerConfigWillLoad(),同步调用configWillLoad、configDidLoad方法:

    // egg-core/lib/lifecycle.js
    
    triggerConfigWillLoad() {
        for (const boot of this[BOOTS]) {
          if (boot.configWillLoad) {
            boot.configWillLoad();
          }
        }
        this.triggerConfigDidLoad();
      }

    关键来了,上一步触发了configDidLoad,接下来触发didLoad方法,代码是这样的:

    // egg-core/lib/lifecycle.js
    
      triggerDidLoad() {
        debug('register didLoad');
        for (const boot of this[BOOTS]) {
          const didLoad = boot.didLoad && boot.didLoad.bind(boot);
          if (didLoad) {
            this[REGISTER_READY_CALLBACK]({
              scope: didLoad,
              ready: this.loadReady,
              timingKeyPrefix: 'Did Load',
              scopeFullName: boot.fullPath + ':didLoad',
            });
          }
        }
      }
    
    boot.fullPath是该agent.js的全路径;

    再到REGISTER_READY_CALLBACK方法:

    // egg-core/lib/lifecycle.js
    
      [REGISTER_READY_CALLBACK]({ scope, ready, timingKeyPrefix, scopeFullName }) {
    
        const done = ready.readyCallback(scopeFullName);
        process.nextTick(() => {
          utils.callFn(scope).then(() => {
            done();
          }, err => {
            done(err);
          });
        });
      }

    调用了loadReady.readyCallBack方法,传入一个全路径标识符,去ready-callback找吧:

    // ready-callback/lib/ready.js
    
      readyCallback(name, opt) {
    
        opt = Object.assign({}, defaults, this.opt, opt);
        const cacheKey = uuid.v1();
        opt.name = name || cacheKey;
        const timer = setTimeout(() => this.emit('ready_timeout', opt.name), opt.timeout);
        
        const cb = once(err => {
          if (err != null && !(err instanceof Error)) {
            err = new Error(err);
          }
          clearTimeout(timer);
          // won't continue to fire after it's error
          if (this.isError === true) return;
          // fire callback after all register
          setImmediate(() => this.readyDone(cacheKey, opt, err));
        });
        
        debug('[%s] Register task id `%s` with %j', cacheKey, opt.name, opt);
        cb.id = opt.name;
        this.cache.set(cacheKey, cb);
        return cb;
      }

    只传入了一个路径标识,没有传函数执行体,所以这个函数肯定是用来控制流程的。来了个once模块,传入回调,搭配wrappy模块,作用是保证只执行一次传入的回调。

    返回后,注册process.nextTick microtask:

    // egg-core/lib/lifecycle.js
    
      [REGISTER_READY_CALLBACK]({ scope, ready, timingKeyPrefix, scopeFullName }) {
    
        const done = ready.readyCallback(scopeFullName);
        process.nextTick(() => {
          utils.callFn(scope).then(() => {
            done();
          }, err => {
            done(err);
          });
        });
      }

    此时,同步代码执行完毕,事件循环转到下一阶段,开始执行process.nextTick的任务,里面的任务就是didLoad方法,didLoad是异步方法,执行完成didLoad方法,再执行done,done就是上面的传入once的方法体:

    // ready-callback/lib/ready.js    
    
            const cb = once(err => {
          if (err != null && !(err instanceof Error)) {
            err = new Error(err);
          }
          clearTimeout(timer);
          // won't continue to fire after it's error
          if (this.isError === true) return;
          // fire callback after all register
          setImmediate(() => this.readyDone(cacheKey, opt, err));
        });

    再次异步调用readyDone方法:

    // ready-callback/lib/ready.js
    
      readyDone(id, opt, err) {
        this.cache.delete(id);
    
        this.emit('ready_stat', {
          id: opt.name,
          remain: getRemain(this.cache),
        });
    
        if (this.cache.size === 0) {
          this.ready(true);
        }
        return this;
      }

    如果发现此时,已经没有didLoad方法了,即this.cache.size === 0,就触发ready方法,而在lifecycle里,实例化loadReady时,早就注册了ready方法:

    // egg-core/lib/lifecycle.js
        this.loadReady.ready(err => {
          debug('didLoad done');
          if (err) {
            this.ready(err);
          } else {
            this.triggerWillReady();
          }
        });

    此时,ready方法触发,执行域是在ilfecycle,调用triggerWillReady方法,继续往下走生命周期方法,这样就串起来了。

    总结来说,egg同阶段的生命周期方法都是异步调用(通过process.nextTick调用),只有等最后那个异步方法调用完成,才触发下一个阶段。

    其实可以做的更简单直观点,相当于各个模块之间dependency,执行有个先后顺序。

  • 相关阅读:
    LIGHTSWITCH 的树控件设计!
    企业即时通信平台与在线办公!
    特殊符号网 http://www.teshufuhao.net/
    国内主要工作流厂商分析 (转)
    衣联网 http://www.eelly.com/ 中国最大的专业服装批发市场(广州)
    NET FrameWork & CLR & C# 运行基本原理
    通过负载均衡器+域名实现容灾切换(2)DNS解析过程理解
    SPAN和RSPAN概念
    【转】一张图看懂开源许可协议,开源许可证GPL、BSD、MIT、Mozilla、Apache和LGPL的区别
    SIP Recording(录制)相关协议
  • 原文地址:https://www.cnblogs.com/cool-fire/p/14505237.html
Copyright © 2020-2023  润新知