深入理解Webpack核心模块Tapable钩子[异步版]

cocoer 2019-07-01

接上一篇文章 深入理解Webpack核心模块WTApable钩子(同步版)

tapable中三个注册方法

  • 1 tap(同步) 2 tapAsync(cb) 3 tapPromise(注册的是Promise)

tapable中对三个触发方法

  • 1 call 2 callAsync 3 promise

这一章节 我们将分别实现异步的Async版本和Promise版本

异步钩子
  • AsyncParallelHook
  • AsyncParallelHook的Promise版本
  • AsyncSeriesHook
  • AsyncSeriesHook的Promise版本
  • AsyncSeriesWaterfallHook
  • AsyncSeriesWaterfallHook的Promise版本

异步的钩子分为并行和串行的钩子,并行是指 等待所有并发的异步事件执行之后再执行最终的异步回调。
而串行是值 第一步执行完毕再去执行第二步,以此类推,直到执行完所有回调再去执行最终的异步回调。

AsyncParallelHook

AsyncParallelHook是异步并行的钩子,上代码:

const { AsyncParallelHook } = require('tapable');

class Hook{
    constructor(){
        this.hooks = new AsyncParallelHook(['name']);
    }
    tap(){
        /** 异步的注册方法是tapAsync() 
         * 并且有回调函数cb.
        */
        this.hooks.tapAsync('node',function(name,cb){
            setTimeout(()=>{
                console.log('node',name);
                cb();
            },1000);
        });
        this.hooks.tapAsync('react',function(name,cb){
            setTimeout(()=>{
                console.log('react',name);
                cb();
            },1000);
        });
    }
    start(){
        /** 异步的触发方法是callAsync() 
         * 多了一个最终的回调函数 fn.
        */
        this.hooks.callAsync('call end.',function(){
            console.log('最终的回调');
        });
    }
}

let h = new Hook();

h.tap();/** 类似订阅 */
h.start();/** 类似发布 */

/* 打印顺序:
    node call end.
    react call end.
    最终的回调
*/

等待1s后,分别执行了node call end和react callend 最后执行了最终的回调fn.

手动实现:

class AsyncParallelHook{
    constructor(args){ /* args -> ['name']) */
        this.tasks = [];
    }
    /** tap接收两个参数 name和fn */
    tap(name,fn){
        /** 订阅:将fn放入到this.tasks中 */
        this.tasks.push(fn);
    }
    start(...args){
        let index = 0;
        /** 通过pop()获取到最后一个参数 
         * finalCallBack() 最终的回调
         */
        let finalCallBack = args.pop();
        /** 箭头函数绑定this */
        let done = () => {
            /** 执行done() 每次index+1 */
            index++;
            if(index === this.tasks.length){
                /** 执行最终的回调 */
                finalCallBack();
            }
        }
        this.tasks.forEach((task)=>{
            /** 执行每个task,传入我们给定的done回调函数 */
            task(...args,done);
        });
    }
}

let h = new AsyncParallelHook(['name']);

/** 订阅 */
h.tap('react',(name,cb)=>{
    setTimeout(()=>{
        console.log('react',name);
        cb();
    },1000);
});
h.tap('node',(name,cb)=>{
    setTimeout(()=>{
        console.log('node',name);
        cb();
    },1000);
});
/** 发布 */
h.start('end.',function(){
    console.log('最终的回调函数');
});

/* 打印顺序:
    react end.
    node end.
    最终的回调函数
*/
AsyncParallelHook的Promise版本
const { AsyncParallelHook } = require('tapable');

class Hook{
    constructor(){
        this.hooks = new AsyncParallelHook(['name']);
    }
    tap(){
        /** 这里是Promsie写法 
         * 注册事件的方法为tapPromise 
        */
        this.hooks.tapPromise('node',function(name){
            return new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    console.log('node',name);
                    resolve();
                },1000);
            });
        });
        this.hooks.tapPromise('react',function(name){
            return new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    console.log('react',name);
                    resolve();
                },1000);
            });
        });
    }
    start(){
        /** 
         * promsie最终返回一个prosise 成功resolve时
         * .then即为最终回调
        */
        this.hooks.promise('call end.').then(function(){
            console.log('最终的回调');
        });
    }
}

let h = new Hook();

h.tap();
h.start();

/* 打印顺序:
    node call end.
    react call end.
    最终的回调
*/

这里钩子还是AsyncParallelHook钩子,只是写法变成了promise的写法,去掉了回调函数cb().变成了成功时去resolve().其实用Promise可以更好解决异步并行的问题,因为Promise的原型方法上有个all()方法,它的作用就是等待所有promise执行完毕后再去执行最终的promise。我们现在去实现它:

class SyncHook{
    constructor(args){ 
        this.tasks = [];
    }
    tapPromise(name,fn){
        this.tasks.push(fn);
    }
    promise(...args){
        /** 利用map方法返回一个新数组的特性 */
        let tasks = this.tasks.map((task)=>{
            /** 每一个task都是一个Promise */
            return task(...args);
        });
        /** Promise.all() 等待所有Promise都执行完毕 */
        return Promise.all(tasks);
    }
}

let h = new SyncHook(['name']);

/** 订阅 */
h.tapPromise('react',(name)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log('react',name);
            resolve();
        },1000);
    });
});
h.tapPromise('node',(name)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log('node',name);
            resolve();
        },1000);
    });
});
/** 发布 */
h.promise('end.').then(function(){
    console.log('最终的回调函数');
});

/* 打印顺序:
    react end.
    node end.
    最终的回调函数
*/
AsyncSeriesHook

AsyncSeriesHook是异步串行的钩子, 串行,我们刚才说了, 它是一步步去执行的,下一步执行依赖上一步执行是否完成,手动实现:

const { AsyncSeriesHook } = require('tapable');

class Hook{
    constructor(){
        this.hooks = new AsyncSeriesHook(['name']);
    }
    tap(){
        /** 异步的注册方法是tapAsync() 
         * 并且有回调函数cb.
        */
        this.hooks.tapAsync('node',function(name,cb){
            setTimeout(()=>{
                console.log('node',name);
                cb();
            },1000);
        });
        this.hooks.tapAsync('react',function(name,cb){
            /** 此回调要等待上一个回调执行完毕后才开始执行 */
            setTimeout(()=>{
                console.log('react',name);
                cb();
            },1000);
        });
    }
    start(){
        /** 异步的触发方法是callAsync() 
         * 多了一个最终的回调函数 fn.
        */
        this.hooks.callAsync('call end.',function(){
            console.log('最终的回调');
        });
    }
}

let h = new Hook();

h.tap();
h.start();

/* 打印顺序:
    node call end.
    react call end. -> 1s后打印
    最终的回调 -> 1s后打印
*/

AsyncParallelHook和AsyncSeriesHook的区别是AsyncSeriesHook是串行的异步钩子,也就是说它会等待上一步的执行 只有上一步执行完毕了 才会开始执行下一步。而AsyncParallelHook是并行异步 AsyncParallelHook 是同时并发执行。 ok.手动实现 AsyncSeriesHook:

class AsyncParallelHook{
    constructor(args){ /* args -> ['name']) */
        this.tasks = [];
    }
    /** tap接收两个参数 name和fn */
    tap(name,fn){
        /** 订阅:将fn放入到this.tasks中 */
        this.tasks.push(fn);
    }
    start(...args){
        let index = 0;
        let finalCallBack = args.pop();
        /** 递归执行next()方法 直到执行所有task
         * 最后执行最终的回调finalCallBack()
         */
        let next = () => {
            /** 直到执行完所有task后 
             * 再执行最终的回调 finalCallBack()
             */
            if(index === this.tasks.length){
                return finalCallBack();
            }
            /** index++ 执行每一个task 并传入递归函数next
             * 执行完每个task后继续递归执行下一个task
             * next === cb,next就是每一步的cb回调
            */
            this.tasks[index++](...args,next);
        }
        /** 执行next() */
        next();
    }
}

let h = new AsyncParallelHook(['name']);

/** 订阅 */
h.tap('react',(name,cb)=>{
    setTimeout(()=>{
        console.log('react',name);
        cb();
    },1000);
});
h.tap('node',(name,cb)=>{
    setTimeout(()=>{
        console.log('node',name);
        cb();
    },1000);
});
/** 发布 */
h.start('end.',function(){
    console.log('最终的回调函数');
});

/* 打印顺序:
    react end.
    node end. -> 1s后打印
    最终的回调函数 -> 1s后打印
*/
AsyncSeriesHook的Promise版本
const { AsyncSeriesHook } = require('tapable');

class Hook{
    constructor(){
        this.hooks = new AsyncSeriesHook(['name']);
    }
    tap(){
        /** 这里是Promsie写法 
         * 注册事件的方法为tapPromise 
        */
        this.hooks.tapPromise('node',function(name){
            return new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    console.log('node',name);
                    resolve();
                },1000);
            });
        });
        this.hooks.tapPromise('react',function(name){
            /** 等待上一步 执行完毕之后 再执行 */
            return new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    console.log('react',name);
                    resolve();
                },1000);
            });
        });
    }
    start(){
        /** 
         * promsie最终返回一个prosise 成功resolve时
         * .then即为最终回调
        */
        this.hooks.promise('call end.').then(function(){
            console.log('最终的回调');
        });
    }
}

let h = new Hook();

h.tap();
h.start();

/* 打印顺序:
    node call end.
    react call end. -> 1s后打印
    最终的回调 -> 1s后打印
*/

手动实现AsyncSeriesHook的Promise版本

class AsyncSeriesHook{
    constructor(args){ 
        this.tasks = [];
    }
    tapPromise(name,fn){
        this.tasks.push(fn);
    }
    promise(...args){
        /** 1 解构 拿到第一个first
         * first是一个promise
         */
        let [first, ...others] = this.tasks;
        /** 4 利用reduce方法 累计执行
         * 它最终返回的是一个Promsie
         */
        return others.reduce((l,n)=>{
            /** 1 下一步的执行依赖上一步的then */
            return l.then(()=>{
                /** 2 下一步执行依赖上一步结果 */
                return n(...args);
            });
        },first(...args));
    }
}

let h = new AsyncSeriesHook(['name']);

/** 订阅 */
h.tapPromise('react',(name)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log('react',name);
            resolve();
        },1000);
    });
});
h.tapPromise('node',(name)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log('node',name);
            resolve();
        },1000);
    });
});
/** 发布 */
h.promise('end.').then(function(){
    console.log('最终的回调函数');
});

/* 打印顺序:
    react end.
    node end. -> 1s后打印
    最终的回调函数 -> 1s后打印
*/

最后一个AsyncSeriesWaterfallHook:

AsyncSeriesWaterfallHook

AsyncSeriesWaterfallHook 异步的串行的瀑布钩子,首先 它是一个异步串行的钩子,同时 它的下一步依赖上一步的结果返回:

const { AsyncSeriesWaterfallHook } = require('tapable');

class Hook{
    constructor(){
        this.hooks = new AsyncSeriesWaterfallHook(['name']);
    }
    tap(){
        this.hooks.tapAsync('node',function(name,cb){
            setTimeout(()=>{
                console.log('node',name);
                /** 第一次参数是err, 第二个参数是传递给下一步的参数 */
                cb(null,'第一步返回第二步的结果');
            },1000);
        });
        this.hooks.tapAsync('react',function(data,cb){
            /** 此回调要等待上一个回调执行完毕后才开始执行 
             *  并且 data 是上一步return的结果.
            */
            setTimeout(()=>{
                console.log('react',data);
                cb();
            },1000);
        });
    }
    start(){
        this.hooks.callAsync('call end.',function(){
            console.log('最终的回调');
        });
    }
}

let h = new Hook();

h.tap();
h.start();

/* 打印顺序:
    node call end.
    react 第一步返回第二步的结果
    最终的回调
*/

我们可以看到 第二步依赖了第一步返回的值, 并且它也是串行的钩子,实现它:

class AsyncParallelHook{
    constructor(args){ /* args -> ['name']) */
        this.tasks = [];
    }
    /** tap接收两个参数 name和fn */
    tap(name,fn){
        /** 订阅:将fn放入到this.tasks中 */
        this.tasks.push(fn);
    }
    start(...args){
        let index = 0;
        /** 1 拿到最后的最终的回调 */
        let finalCallBack = args.pop();
        let next = (err,data) => {
            /** 拿到每个task */
            let task = this.tasks[index];
            /** 2 如果没传task 或者全部task都执行完毕
             * return 直接执行最终的回调finalCallBack()
             */
            if(!task) return finalCallBack();

            if(index === 0){
                /** 3 执行第一个task
                 * 并传递参数为原始参数args
                 */
                task(...args, next);
            }else{
                /** 4 执行处第二个外的每个task
                 * 并传递的参数 data
                 *  data ->‘传递给下一步的结果’
                 */
                task(data, next);
            }
            index++;
        }
        /** 执行next() */
        next();
    }
}

let h = new AsyncParallelHook(['name']);

/** 订阅 */
h.tap('react',(name,cb)=>{
    setTimeout(()=>{
        console.log('react',name);
        cb(null,'传递给下一步的结果');
    },1000);
});
h.tap('node',(name,cb)=>{
    setTimeout(()=>{
        console.log('node',name);
        cb();
    },1000);
});
/** 发布 */
h.start('end.',function(){
    console.log('最终的回调函数');
});

/* 打印顺序:
    react end.
    node 传递给下一步的结果
    最终的回调函数
*/
AsyncSeriesWaterfallHook的Promise版本
const { AsyncSeriesWaterfallHook } = require('tapable');

class Hook{
    constructor(){
        this.hooks = new AsyncSeriesWaterfallHook(['name']);
    }
    tap(){
        this.hooks.tapPromise('node',function(name){
            return new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    console.log('node',name);
                    /** 在resolve中把结果传给下一步 */
                    resolve('返回给下一步的结果');
                },1000);
            });
        });
        this.hooks.tapPromise('react',function(name){
            return new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    console.log('react',name);
                    resolve();
                },1000);
            });
        });
    }
    start(){
        this.hooks.promise('call end.').then(function(){
            console.log('最终的回调');
        });
    }
}

let h = new Hook();

h.tap();
h.start();

/* 打印顺序:
    node call end.
    react 返回给下一步的结果
    最终的回调
*/

用Promsie实现很简单,手动实现它吧:

class AsyncSeriesHook{
    constructor(args){ 
        this.tasks = [];
    }
    tapPromise(name,fn){
        this.tasks.push(fn);
    }
    promise(...args){
        /** 1 解构 拿到第一个first
         * first是一个promise
         */
        let [first, ...others] = this.tasks;
        /** 2 利用reduce方法 累计执行
         * 它最终返回的是一个Promsie
         */
        return others.reduce((l,n)=>{
            return l.then((data)=>{
                /** 3 将data传给下一个task 即可 */
                return n(data);
            });
        },first(...args));
    }
}

let h = new AsyncSeriesHook(['name']);

/** 订阅 */
h.tapPromise('react',(name)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log('react',name);
            resolve('promise-传递给下一步的结果');
        },1000);
    });
});
h.tapPromise('node',(name)=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log('node',name);
            resolve();
        },1000);
    });
});
/** 发布 */
h.promise('end.').then(function(){
    console.log('最终的回调函数');
});

/* 打印顺序:
    react end.
    node promise-传递给下一步的结果
    最终的回调函数
*/

ok.至此,我们把tapable的钩子全部解析并手动实现完毕。写文章不易,喜欢的话给个赞或者start~
代码在github上:mock-webpack-tapable

相关推荐