专栏首页腾讯IVWEB团队的专栏yeoman-generator 中的 run loop 实现
原创

yeoman-generator 中的 run loop 实现

本文作者: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,队列都会执行一次:

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对象的属性,可以利用订阅发布来调用事件。 先来看整体的实现(代码有删减):

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的任务数组中添加新的方法元素。上面的例子就会有如下结构

{
    first: [first task1, first task2]
    second: [second func]
    thrid: [thrid func]
}

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

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的生命周期实现,首先定义任务队列:

const queues = [
  'initializing',
  'prompting',
  'configuring',
  'default',
  'writing',
  'conflicts',
  'install',
  'end'
];

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

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类:

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社区

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

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Canvas 实现 progress 效果

    分享下一个简单的Canvas插件 ,Canvas元素是HTML5的一部分,允许脚本语言动态渲染位图像,你可以使用javascript用它来绘制图形。

    腾讯IVWEB团队
  • 前端 fetch 通信

    随着前端异步的发展, XHR 这种耦合方式的书写不利于前端异步的 Promise 回调。而且,写起来也是很复杂.。fetch API 本来是在 SW(Servi...

    腾讯IVWEB团队
  • 《GraphQL 名词 101:解析 GraphQL 的查询语法》【译】

    GraphQL 日渐成为数据查询的主流标准之一。每天都会产生许多围绕这项技术发展的精彩讨论和新工具。GraphQL最棒的特性就是提供了一个丰富语言集来描述获取数...

    腾讯IVWEB团队
  • 11-51单片机ESP8266学习-AT指令(单片机采集温湿度数据通过8266发送给AndroidTCP客户端显示)

    http://www.cnblogs.com/yangfengwu/p/8798512.html 先把源码和资料链接放到这里 链接:https://pan.ba...

    杨奉武
  • How to create CDS view to return Service order item detail data

    版权声明:本文为博主汪子熙原创文章,未经博主允许不得转载。 https://jerry.bl...

    Jerry Wang
  • 树莓派64位系统Debian 9先行测评:性能最高提升30倍

    采用64位处理器的树莓派3B,虽然具有64位硬件,但是系统还没有跟上节奏。官方尚未正式发布64位Raspbian,近期有团队移植了Debian 9 arm64到...

    Debian社区
  • 库克将苹果翻个底朝天后怒了!要求彭博撤回涉及中国芯片的报道

    此前不久《彭博商业周刊》搞了一个大新闻,指责中国间谍恶意将芯片植入硅谷服务器,从而创建网上“隐形门”,大约30家美国公司受害。

    量子位
  • 理解谱聚类

    聚类是典型的无监督学习问题,其目标是将样本集划分成多个类,保证同一类的样本之间尽量相似,不同类的样本之间尽量不同,这些类称为簇(cluster)。与有监督的分类...

    SIGAI学习与实践平台
  • 旅游“加速度” 云南为何走在了文旅融合的前列?

    ? 今年,云南旅游又一次成为了国庆黄金周的爆款,从苍山洱海到玉龙雪山,从白天的丽江到夜晚的西双版纳,云南文旅经历了一次次的升级换代,迎来了一波又一波的游客,其...

    腾讯文旅
  • seo文章标题怎么写?

    SEO优化其中的SEO标题优化也是一项重要的工作,如果一个网站的SEO标题优化得好的吗那么这个网站排名也是相当快速的,像之前说的《文章标题写法!对关键词排名最大...

    申霖

扫码关注云+社区

领取腾讯云代金券