前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >yeoman-generator 中的 run loop 实现

yeoman-generator 中的 run loop 实现

原创
作者头像
腾讯IVWEB团队
修改2017-09-21 09:52:33
8850
修改2017-09-21 09:52:33
举报

本文作者:ivweb qcyhust

导语

在上一篇yeoman(利用yeoman构建项目generator)的构建项目介绍文中提到过一个yeoman genenrator的run loop。当时提到“每一个添加进去的方法都会在generator调用的时候被调用,而且通常来讲,这些方法是按照顺序调用的”以及简单介绍了yeoman的方法执行顺序,这篇文章将仔细分析run loop的具体实现。

run loop

所谓的run loop是IOS开发中的一个概念,具体来说是一个与线程相对应的对象,用它来实现线程自动释放池、延迟回调、触摸事件、屏幕刷新等功能。线程一般在执行完任务后就直接退出,run loop这个循环会让线程处于接受消息->等待->处理的循环中,直到接受到退出的信号才会结束循环。 yeoman中的run loop概念是说存在多个generator时,在我们给每一个genenrator类都定义了一系列具有优先级关系的属性事件用于构建不同的项目文件,每一次实例化genenrator的时候运行我们的构建程序,多个generator的组合使用就需要一个run loop处理来接收用户发出的构建事件,等待用户输入,按优先级的顺序处理构建程序的循环。 参考Run Loops

核心库Grouped-queue

yeoman使用Grouped-queue来处理run loop。yeoman有自己的事件生命周期,在前文中提到过,按照顺序列出来是initializing,prompting,configuring,default,writing,conflicts,install,end,开发者在generator中定义的方法名如果不在上面列出的事件中,那么将作为defalut事件,在configuring和writing中间被调用。 Grouped-queue用来管理一个存在于内存中的任务队列,引用库后返回一个构造函数。 const queue = require('grouped-queue'); queue接受一个可配置的数组也就是任务组作为参数,任务组中的元素为字符串,第一个字符串将是第一个调用的任务,第二个字符串是第二个任务,以此类推。 实例queue有一个add方法add( [group], task, [options] ),向任务组中添加任务,参数:

  • 任务名
  • 任务方法
  • 配置对象 如果没有指定组的名字,会使用default。每一个任务方法都会收到一个callback作为参数,这个callback必须在定义的任务中被调用来进入下一个任务。// 向任务队列中增加writing任务、 queue.add('writing', function( cb ) { /* 一些完成一些事情,同步或异步, * 如果是同步则在最后调用cb * 如果是异步,则在异步回调中调用cb */ });

这样就可以构建一个任务队列,事件将按顺序被调用,每次调用add,队列都会执行一次:

代码语言:javascript
复制
const queue = new GroupedQueue(['first', 'second', 'thrid']);

queue.add('first', (callback) => {
    console.log('the first task1');
    callback();
});

queue.add('first', (callback) => {
    console.log('the first task2');
    callback();
});

queue.add('thrid', (callback) => {
    console.log('the thrid task');
    callback();
});

queue.add('second', (callback) => {
    console.log('the second task');
    setTimeout(callback, 1000);
});

// 按顺序输出 first1 first2 second thrid 任务

队列实现

Grouped-queue的源码并不复杂,其核心就是维持一个对列对象,这个对象已任务名为key,以任务数组为value,然后按照任务对列的顺序调用。Queue继承了EventEmitter对象的属性,可以利用订阅发布来调用事件。 先来看整体的实现(代码有删减):

代码语言:javascript
复制
function Queue( subQueues ) {
      this.queueNames = subQueues;

    // 存放任务数组
     this.__queues__ = {};

    subQueues.forEach(function( name ) {
           this.__queues__[name] = new SubQueue();
    }.bind(this));
}

function SubQueue() {
    // 每一个任务的子任务
      this.__queue__ = [];
}

可以看出Queue用私有属性queues对象维护任务队列,我们传入的任务队列在内部用于生成任务数组subQueue的queue数组,调用add方法实际上就是往queues对象中的相应key的任务数组中添加新的方法元素。上面的例子就会有如下结构

代码语言:javascript
复制
{
    first: [first task1, first task2]
    second: [second func]
    thrid: [thrid func]
}

在看看运行(代码有删减):

代码语言:javascript
复制
Queue.prototype.run = function() {
    if ( this.running ) return;
      this.running = true;

    this._exec(function() {
        this.running = false;
        // 任务队列中的任务都运行完就触发end事件
        if (_(this.__queues__).map('__queue__').flatten().value().length === 0) {
              this.emit('end');
        }
    }.bind(this));
};

Queue.prototype._exec = function( done ) {
      var pointer = -1;
    // 取出任务队列
    var names = Object.keys( this.__queues__ );

    var next = function next() {
        pointer++;
        // 没有任务了执行done
        if ( pointer >= names.length ) return done();
        // 执行相应key下的subQueue的run方法
        this.__queues__[ names[pointer] ].run( next.bind(this), this._exec.bind(this, done) );
    }.bind(this);

    next();
};

/*
* SubQueue实例的run方法
* $param skip 跳过执行Queue._exec的next
* $param done 指向Queue._exec,从头执行任务队列
*/
SubQueue.prototype.run = function( skip, done ) {
     // 如果数组中没有方法元素就跳过
      if ( this.__queue__.length === 0 ) return skip();
    // 取出数组中的方法,将done也就是callback作为第一个参数传入
      setImmediate( this.shift().task.bind(null, done) );
};

每次调用callback任务队列会重新执行一次。每次重新执行时,之前的任务的数据已经都是空的,所以会直接skip跳过,执行下一个next。每一个任务都是使用setImmediate在下一个事件循环中调用,Grouped Queue中添加了一个标志running,在run方法中判断,如果是runing状态则直接返回,不会调用exec,等到callback调用才会执行exec来保证执行顺序。

简单实现yeoman的生命周期

明白了Grouped Queue的使用方法后原理后可以简单模拟yeoman genenrator的生命周期实现,首先定义任务队列:

代码语言:javascript
复制
const queues = [
  'initializing',
  'prompting',
  'configuring',
  'default',
  'writing',
  'conflicts',
  'install',
  'end'
];

然后提供一个基类给每一个genenrator继承:

代码语言:javascript
复制
const GroupedQueue = require('grouped-queue');
const runAsync = require('run-async');

class Base {
    constructor() {
        this.runLoop = new GroupedQueue(queues);

        this.run();
    }

    run() {
        const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
        const isValidMethods = (method) => method.charAt(0) !== '_' && method !== 'constructor';
        // 过滤方法名
        const validMethods = methods.filter(isValidMethods);
        const self = this;

        validMethods.forEach((method) => {
            const taskFunc = this[method];
            let methodName = method;

            // 自定义方法归入defalut队列
            if(!queues.includes(method)) {
                methodName = 'default';
            }

            this.runLoop.add(methodName, (next) => {
                // 处理异步事件
                runAsync(function () {
                    return taskFunc.apply(self);
                })().then(next);
            });
        })
    }
}

实现一个genenrator类:

代码语言:javascript
复制
class Test extends Base {
    constructor(args) {
        super(args);

        console.log('constructor');
    }

    initializing() {
        return new Promise((done, reject) => {
            setTimeout(() => {
                console.log('initializing');
                done()
            }, 1000);
        })
    }

    writing() {
        console.log('writing');
    }

    selfdefine() {
        return new Promise((done, reject) => {
            setTimeout(() => {
                console.log('selfdefine');
                done()
            }, 2000);
        })
    }

    install() {
        console.log('install');
    }
}

const test = new Test();
// 输出  constructor
         initializing 隔1秒
         selfdefine   隔2秒
         writing
         install

原文出处:IVWEB社区

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导语
  • run loop
  • 核心库Grouped-queue
  • 队列实现
  • 简单实现yeoman的生命周期
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档