深刻理解Webpack核心模塊Tapable鉤子[異步版]

接上一篇文章 深刻理解Webpack核心模塊WTApable鉤子(同步版)node

tapable中三個註冊方法react

  • 1 tap(同步) 2 tapAsync(cb) 3 tapPromise(註冊的是Promise)

tapable中對三個觸發方法webpack

  • 1 call 2 callAsync 3 promise

這一章節 咱們將分別實現異步的Async版本和Promise版本git

異步鉤子
  • AsyncParallelHook
  • AsyncParallelHook的Promise版本
  • AsyncSeriesHook
  • AsyncSeriesHook的Promise版本
  • AsyncSeriesWaterfallHook
  • AsyncSeriesWaterfallHook的Promise版本

異步的鉤子分爲並行和串行的鉤子,並行是指 等待全部併發的異步事件執行以後再執行最終的異步回調。
而串行是值 第一步執行完畢再去執行第二步,以此類推,直到執行完全部回調再去執行最終的異步回調。github

AsyncParallelHook

AsyncParallelHook是異步並行的鉤子,上代碼:web

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.segmentfault

手動實現:數組

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。咱們如今去實現它: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

相關文章
相關標籤/搜索