Skip to content

Tapable原理分析(一) #72

Description

@xiaoxiaosaohuo

以下是tapable暴露的所有hooks类型。

exports.__esModule = true;
exports.Tapable = require("./Tapable");
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");

注册事件回调

  • tap
  • tapAsync
  • tapPromise

tapAsync和tapPromise 不能用于sync开头的hook,

执行顺序

在注册事件回调时,配置对象可以改变执行顺序

  • before。其类型可以是数组也可以是一个字符串,传入的是注册事件回调的名称
  • stage。其类型是数字,数字越小事件回调执行的时机越早
hook.tap({
    name: 'first',
}, (params) => {
    console.log('first')
})

hook.tap({
    name: 'second',
    before: 'first',
}, (params) => {
    console.log('second')
})

hook.tap({
 name: 'third',
 stage: -1
}, (params) => {
 console.log('third')
})

hook.call('tapable', 'siven')

//输出

third
second
first

事件触发

监听有三种方式,同样的,触发也有三种方式。

  • call ,对应tap
  • callAsync,对应tapAsync
  • promise,对应tapPromise

call

hook.tap({
 name: 'frist',
}, (params) => {
 console.log('third')
})

hook.call('tapable', 'siven')

callAsync

hook.tapAsync("first", (name, callback) => {
  console.log("first", name);
  callback();
});
hook.tapAsync("second", (name, callback) => {
  console.log("second", name);
  callback();
});
hook.callAsync("callAsync", (error, result) => {
  console.log("callAsync", error, result);
});

事件回调中的callback必须要执行,否则不会执行后续的事件回调和 callAsync 传入的回调。

这个callback经过了一层封装,其内部逻辑是:

if(_err1) {
     _callback(_err1);
} else {
    _next1();
 }

_next1是下一个监听回调,对应本例子中second的回调。
_callback 是callAsync的回调函数。

如果 callback 执行时不传入值,就会继续执行后续的事件回调。
。如果传入错误信息,就会直接执行 _callback,不再执行后续的事件回调。

promise

promise 执行之后会返回一个 Promise 对象。在使用 tapPromise 注册事件回调时,事件回调必须返回一个 Promise 对象,否则会报错

hook.tapPromise("first", name => {
  console.log("first", name);

  return Promise.resolve("first");
});

hook.tapPromise("second", name => {
  console.log("second", name);

  return Promise.resolve("second");
});

const promise = hook.promise("promise");

promise.then(
  value => {
    console.log("value", value);
  },
  reason => {
    console.log("reason", reason);
  }
);

hook的其他参数

Interception

所有的hooks都能添加额外的interception参数

interface Tap {
        name: string,  // 标记每个 handler,必须有,
        before: string | array, // 插入到指定的 handler 之前
        type: string,   // 类型:'sync', 'async', 'promise'
        fn: Function,   // handler
        stage: number,  // handler 顺序的优先级,默认为 0,越小的排在越前面执行
        context: boolean // 内部是否维护 context 对象,这样在不同的 handler 就能共享这个对象
}
hook.tap({
    name: 'first',
}, (params) => {
    console.log('first')
})

hook.tap({
    name: 'second',
    before: 'first',
}, (params) => {
    console.log('second')
})

hook.tap({
 name: 'third',
 stage: -1
}, (params) => {
 console.log('third')
})

hook.call('tapable', 'siven')

//输出
third
second
first

context

插件和拦截器都可以往里面传一个上下文对象的参数,该对象可用于向后续插件和拦截器传递任意值。这个对象会变成回调函数的第一个参数。

const hook = new SyncHook(['name']);
hook.intercept({
  // 启用 context
  context: true,
  register(tap) {
    console.log('register', tap);
    return tap;
  },
  call(...args) {
    console.log('call', args);
  },
  loop(...args) {
    console.log('loop', args);
  },
  tap(context, tap) {
    context.hasContext = true;
    console.log('tap', context, tap);
  },
});

hook.tap(
  {
    name: 'first',
    context: true,
  },
  (context, name) => {
    // context: { hasContext: true }
    console.log('first', context, name);
  }
);

原理模拟实现

SyncHook

同步Hook不关心返回值。调用时将所有的监听函数执行一次。

class SyncHook{
    constructor(){
        this.subs = [];
    }
    tap(fn){
        this.sub.push(fn);
    }
    call(...args){
        this.subs.forEach(fn=>fn(...args));
    }
}



SyncBailHook

如果监听函数返回值不为undefined,剩余监听函数被跳过。

class SyncBailHook{
    constructor(){
        this.subs = [];
    }
    tap(fn){
        this.sub.push(fn);
    }
    call(...args){
        for(let i=0; i< this.subs.length; i++){
            const result = this.subs[i](...args);
            if (result !== undefined){
                break
            }
        }
}

SyncWaterHook

class SyncWaterHook{
    constructor(){
        this.subs = [];
    }
    tap(fn){
        this.sub.push(fn);
    }
    call(...args){
        let result = null;
        for(let i = 0, l = this.hooks.length; i < l; i++) {
            let hook = this.hooks[i];
            result = i == 0 ? hook(...args): hook(result); 
        }
    }
}

SyncLoopHook

返回值不等于undefined就循环执行。

class SyncLooHook{
    constructor(){
        this.subs = [];
    }
    tap(fn){
        this.sub.push(fn);
    }
    call(...args){
      let result;
        do {
            result = this.hook(...arguments);
        } while (result!==undefined)
    }
}

AsyncSeriesHook

class AsyncSeriesHook{
    constructor() {
        this.hooks = [];
    }

    tapAsync(name, fn) {
        this.hooks.push(fn);
    }

    callAsync() {
        var slef = this;
        var args = Array.from(arguments);
        let done = args.pop();
        let idx = 0;

        function next(err) {
            if (err) return done(err);
            let fn = slef.hooks[idx++];
            fn ? fn(...args, next) : done();
        }
        next();
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions