腦殼疼的tapable

百命發表於2019-04-18

tapable聽說了很久,終於下定決心繫統學習一下

Q1:tapable解決的問題?

  1. tapable是個獨立的庫
  2. webpack中大量使用了這個庫
  3. tapable主要是用來處理事件,解決的問題有點類似EventEmitter,不過功能更加強大

Q2:tapable方法有哪些?

const {
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    SyncLoopHook,
    AsyncParallelHook,
    AsyncParallelBailHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook 
 } = require("tapable");
複製程式碼

好的,方法一共是上述這麼多,第一眼看過去,懵逼樹下你和我,所以我們還是一點點來,一個個的分析、學習和了解

Q3:啥是SyncHook?

先來個使用的例子,例如前端開發者需要掌握哪些技能?

step1:首先我們要明確群體是前端開發

const {SyncHook}= require('tapable');
const FontEnd = new SyncHook();
複製程式碼

ok,就是上面這兩句,我們建立了個FontEnd前端開發

step2:前端開發需要掌握哪些技能,例如webpack、react對吧

FontEnd.tap('webpack',()=>{
  console.log("get webpack")
});
FontEnd.tap('react',()=>{
  console.log("get react")
});
複製程式碼

ok,上面的tap就是用來繫結事件的,為前端開發新增了兩個技能

step3:技能需要學習才能掌握,所以我們要有學習的動作

FontEnd.learn=()=>{
  FontEnd.call()
};
FontEnd.learn();
複製程式碼

step4:檢視執行結果

get webpack
get react
複製程式碼

可以看到,通過上面的呼叫,我們的前端開發已經學會了react、webpack

step5:傳參

前面知道FontEnd這個群體,需要學react、webpack,但落到個人角度,究竟哪一個開發者掌握這些技能了呢?

const {SyncHook}= require('tapable');
const FontEnd = new SyncHook();
FontEnd.tap('webpack',(name)=>{
  console.log(name+" get webpack")
});
FontEnd.tap('react',(name)=>{
  console.log(name+" get react")
});
FontEnd.start=(name)=>{
  FontEnd.call(name)
};
FontEnd.start('xiaoming');
複製程式碼

修改前面的程式碼,新增引數,預期是輸出xxx get react

step6: 檢視輸出結果

undefined get webpack
undefined get react
複製程式碼

最終結果是undefined,也就是引數沒傳進去

step7:為SyncHook新增約定引數

這是因為const FontEnd = new SyncHook();建立SyncHook的時候沒有約定引數,只要為其新增引數即可,如下:

const {SyncHook}= require('tapable');
const FontEnd = new SyncHook(['name']);// 新增引數約定
FontEnd.tap('webpack',(name)=>{
  console.log(name+" get webpack")
});
FontEnd.tap('react',(name)=>{
  console.log(name+" get react")
});
FontEnd.start=(name)=>{
  FontEnd.call(name)
};
FontEnd.start('xiaoming');
複製程式碼

最終輸出:

xiaoming get webpack
xiaoming get react
複製程式碼

SyncHook總結

  1. SyncHook目前來看比較像訂閱釋出
  2. 就像jquery中的add、fire方法,只不過這裡是tap、call

Q4:SyncHook如何實現?

SyncHook實現比較簡單,就是最簡單的訂閱釋出

class SyncHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tap(name,task){
    this.tasks.push(task);
  }
  call(...args){
    const param =  args.slice(0,this.limit.length);
    this.tasks.forEach(item=>item(...param));
  }
}
複製程式碼
  1. limit是用來做引數校驗的
  2. tasks用來收集訂閱
  3. tap方法用來想tasks中新增方法
  4. call方法,先檢驗引數,然後再執行所有的已訂閱方法

總結:原理比較簡單,沒有太多技術含量,主要就是一個同步的鉤子函式

Q5:啥是SyncBailHook?

熔斷機制,如果前一個事件return true,則不再執行下一個,還是前面的例子:

const {SyncBailHook} =require('tapable');
const FontEnd = new SyncBailHook(['name']);
FontEnd.tap('webpack',(name)=>{
  console.log(name+" get webpack ")
});
FontEnd.tap('react',(name)=>{
  console.log(name+" get react")
});
FontEnd.start=(...args)=>{
  FontEnd.call(...args)
};
FontEnd.start('xiaoming');
複製程式碼

此時,把函式從SyncHook換成SyncBailHook,執行的結果沒有任何區別

but,思考一下,學習很容易會學不下去,所以修改一下我們的例子:

const {SyncBailHook} =require('tapable');
const FontEnd = new SyncBailHook(['name']);
FontEnd.tap('webpack',(name)=>{
  console.log(name+" get webpack ")
  return '學不動了啊!';
});
FontEnd.tap('react',(name)=>{
  console.log(name+" get react")
});
FontEnd.start=(...args)=>{
  FontEnd.call(...args)
};
FontEnd.start('xiaoming');
複製程式碼

此時僅輸出:

xiaoming get webpack 
複製程式碼

後面的react沒有執行

總結:

  1. SyncBailHook主要解決的問題是條件阻塞
  2. 當訂閱事件符合某一判斷時,不再執行下面的流程
  3. 應用場景,場景不斷深入的場景,a、a+b、a+b+c、a+b+c+d這種場景

Q6:SyncBailHook如何實現?

SyncBailHook也十分簡單,還是之前那個例子:

class SyncBailHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tap(name,task){
    this.tasks.push(task);
  }
  call(...args){
    const param =  args.slice(0,this.limit.length);
    this.tasks.some(item=>item(...param));// 只改了一行
  }
}
複製程式碼

可以看到,和上面SyncHook十分相似,無非就是把執行函式forEach,換成some,因為some是阻塞式執行,當返回true,則不會執行後面的內容

Q7:啥是SyncWaterfullHook?

還是先來個使用的例子,例如前端,技能都是一個個學的,要學完webpack再學react,例如:

const {SyncWaterfallHook} = require('tapable');
const FontEnd = new SyncWaterfallHook(['name']);
FontEnd.tap('webpack',(name)=>{
  console.log(name+" get webpack ")
  return '學完webpack了,該學react了';
});
FontEnd.tap('react',(name)=>{
  console.log(name+" get react")
});
FontEnd.start=(...args)=>{
  FontEnd.call(...args)
};
FontEnd.start('xiaoming');
複製程式碼

此時輸出:

xiaoming get webpack 
學完webpack了,該學react了 get react
複製程式碼
  1. SyncWaterfallHook會將前一個任務的執行結果,傳遞給後一個
  2. 主要使用場景是處理邏輯之間相互依賴
  3. 實際效果和redux中的compose方法一毛一樣

Q8:SyncWaterfullHook如何實現?

class SyncWaterfallHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tap(name,task){
    this.tasks.push(task);
  }
  call(...args){
    const param =  args.slice(0,this.limit.length);
    const [first,...others] = this.tasks;
    const ret = first(...param);
    others.reduce((pre,next)=>{
      return next(pre);
    },ret)
  }
}
複製程式碼

SyncWaterfallHook實現也比較簡單

  1. 完全按照redux的compose來實現就行
  2. 第一步,取出第一個執行,並拿到結果ret
  3. 第二步,將結果ret,當作reduce的引數傳遞進去
  4. 第三步,遍歷,不斷把引數傳給下一個函式

總結:SyncWaterfallHook主要還是用於函式之間對結果存在依賴的場景

Q9:啥是SyncLoopHook?

還是前面的例子,如果一次學不懂一門技術,那就要多學幾遍,例如:

const FontEnd = new SyncLoopHook(['name']);
let num = 0;
FontEnd.tap('webpack',(name)=>{
  console.log(name+" get webpack ")
  return ++num === 3?undefined:'再學一次';
});
FontEnd.tap('react',(name)=>{
  console.log(name+" get react")
});
FontEnd.start=(...args)=>{
  FontEnd.call(...args)
};
FontEnd.start('xiaoming');
複製程式碼

上面執行的結果是:

xiaoming get webpack 
xiaoming get webpack 
xiaoming get webpack 
xiaoming get react
複製程式碼
  1. SyncLoopHook任務能夠執行多次
  2. 返回undefined則停止執行,返回非undefined則繼續執行當前任務

總結:主要場景是同一任務,需要執行多次

Q10:SyncLoopHook如何實現?

class SyncLoopHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tap(name,task){
    this.tasks.push(task);
  }
  call(...args){
    const param =  args.slice(0,this.limit.length);
    let index = 0;
    while(index<this.tasks.length){
      const result = this.tasks[index](...param);
      if(result === undefined){
        index++;
      }
    }
  }
}
複製程式碼
  1. 上面的實現是通過計數
  2. 如果結果不為undefined則下標index不移動
  3. 如果結果為undefined則下標index增加

也可以換doWhile來實現

class SyncLoopHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tap(name,task){
    this.tasks.push(task);
  }
  call(...args){
    const param =  args.slice(0,this.limit.length);
   this.tasks.forEach(task=>{
     let ret;
     do{
      ret = task(...param);
     }while(ret!=undefined)
   })
  }
}
複製程式碼
  1. 這種實現沒有下標概念了
  2. 直接遍歷tasks任務組,如果任務組中某一個任務執行的結果不是undefined則再次執行

總結:SyncLoopHook這個使用場景相對較少,不過了解一下也好

Q11:啥是AsyncParralleHook?

前面瞭解的都是同步hook,更關鍵的是非同步hook

舉個例子,同學小王說去學前端了,但你也不知道他什麼時候學完,只有他學完告訴你,你才知道他學完了,例:


const {AsyncParallelHook} = require('tapable');
const FontEnd = new AsyncParallelHook(['name']);
FontEnd.tapAsync('webpack',(name,cb)=>{
  setTimeout(() => {
    console.log(name+" get webpack ")
    cb();
  }, 1000);
 
});
FontEnd.tapAsync('react',(name,cb)=>{
  setTimeout(() => {
    console.log(name+" get react")
    cb();
  }, 1000);
});
FontEnd.start=(...args)=>{
  FontEnd.callAsync(...args,()=>{
    console.log("end");
  })
};
FontEnd.start('小王');
複製程式碼

最終輸出:

小王 get webpack 
小王 get react
end
複製程式碼
  1. AsyncParralleHook是非同步並行鉤子
  2. 使用場景,例如同時發起對兩個介面的請求
  3. 注意:這次註冊事件,不再是tap了,而是tapAsync
  4. 注意:這次的事件執行,不再是call了,而是callAsync
  5. 可以看出tapable中區分了同步、非同步的訂閱和釋出
  6. 注意:想要讓所有非同步執行完成後,接收到通知,需要執行cb()

Q12:AsyncParralleHook如何實現?

class AsyncParallelHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapAsync(name,task){
    this.tasks.push(task);
  }
  callAsync(...args){
    const finalCallBack = args.pop();
    const param =  args.slice(0,this.limit.length);
    let index = 0;
    const done=()=>{
      index++;
      if(index === this.tasks.length){
        finalCallBack();
      }
    }
    this.tasks.forEach(item=>item(...param,done))
  }
}
複製程式碼
  1. AsyncParallelHook最簡單就是通過計數
  2. 在例項上新增一個計數器
  3. 然後遍歷tasks,當任務成功個數與任務總數相同時,執行finalCallBack

總結:AsyncParallelHook解決的問題和promise.all類似,都是用於解決非同步並行的問題

Q13:AsyncParralleHook(2)如何使用promise?

前面雖然用:AsyncParralleHook能夠解決非同步,但並沒有使用primise,也沒有類promise的概念

const {AsyncParallelHook} = require('tapable');
const FontEnd = new AsyncParallelHook(['name']);
FontEnd.tapPromise('webpack',(name)=>{
  return new Promise((resolve)=>{
    setTimeout(() => {
      console.log(name+" get webpack ")
      resolve();
    }, 1000);
  })
});
FontEnd.tapPromise('react',(name,cb)=>{
  return new Promise((resolve)=>{
    setTimeout(() => {
      console.log(name+" get react ")
      resolve();
    }, 1000);
  })
});
FontEnd.start=(...args)=>{
  FontEnd.promise(...args).then(()=>{
    console.log("end");
  })
};
FontEnd.start('小王');
複製程式碼

呼叫上面的api後,輸出:

小王 get webpack 
小王 get react 
end
複製程式碼
  1. 注意:此時繫結事件的方法叫做tapPromise
  2. 注意:此時執行事件的方法叫做promise

總結:

  1. tapable共有三種事件繫結方法:tap、tapAsync、tapPromise
  2. tapable共有三種事件執行方法:call、callAsync、promise

Q14:AsyncParralleHook(2)promise版如何實現?

class AsyncParallelHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapPromise(name,task){
    this.tasks.push(task);
  }
  promise(...args){
    const param =  args.slice(0,this.limit.length);
    const tasks = this.tasks.map(task=>task(...param));
    return Promise.all(tasks)
  }
}
複製程式碼
  1. 核心就是實現兩個方法,tapPromise和promise
  2. tapPromise其實和之前的tap沒有明顯區別(簡單實現的問題)
  3. promise的話,其實就是返回一個Promise.all

Q15:啥是AsyncParallelBailHook?

AsyncParallelBailHook這個鉤子和前面的鉤子不太一樣 按前面的例子來講:

  1. 同學小王說去學前端了,但你也不知道他什麼時候學完,只有他學完告訴你,你才知道他學完了
  2. 小王學了webpack,學崩了,告訴了你
  3. 你聽說小王學崩了,你就以為他學不下去了,你就對大傢伙說,小王學崩了
  4. 但是小王同時也學了react卻咬牙學完了
  5. 雖然學完了,但你已經對外宣佈小王崩了,很打臉,所以就當不知道了

這就是AsyncParallelBailHook處理的事情

const {AsyncParallelBailHook} = require('tapable');
const FontEnd = new AsyncParallelBailHook(['name']);
FontEnd.tapPromise('webpack',(name)=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
      console.log(name+" get webpack ")
      reject('小王學崩了!');
    }, 1000);
  })
});
FontEnd.tapPromise('react',(name,cb)=>{
  return new Promise((resolve)=>{
    setTimeout(() => {
      console.log(name+" get react ")
      resolve();
    }, 2000);
  })
});
FontEnd.start=(...args)=>{
  FontEnd.promise(...args).then(()=>{
    console.log("end");
  },(err)=>{
    console.log("聽說:",err)
  })
};
FontEnd.start('小王');
複製程式碼

上面程式碼執行結果是:

小王 get webpack 
聽說: 小王學崩了!
小王 get react 
複製程式碼
  1. 上面例子,第一個並行任務返回了reject
  2. reject只要不是undefined,就會直接進入promise.all的catch
  3. 非同步任務,react還是會執行,但成功後沒有處理了

再看一個例子:

const {AsyncParallelBailHook} = require('tapable');
const FontEnd = new AsyncParallelBailHook(['name']);
FontEnd.tapPromise('webpack',(name)=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
      console.log(name+" get webpack ")
      reject();
    }, 1000);
  })
});
FontEnd.tapPromise('react',(name,cb)=>{
  return new Promise((resolve)=>{
    setTimeout(() => {
      console.log(name+" get react ")
      resolve();
    }, 2000);
  })
});
FontEnd.start=(...args)=>{
  FontEnd.promise(...args).then(()=>{
    console.log("end");
  },(err)=>{
    console.log("聽說:",err)
  })
};
FontEnd.start('小王');
複製程式碼

和上面就改了1行,就是reject內容為空,此時輸出:

小王 get webpack 
小王 get react 
end
複製程式碼
  1. 此時即便呼叫了reject也不會進入到catch
  2. reject返回空,後面的任務也會照常執行

總結:

  1. AsyncParallelBailHook,如果返回真值,則直接會走進catch
  2. 無論返回結果是什麼,所有任務都會執行
  3. 主要場景是,並行請求3個介面,隨便哪一個返回結果都行,只要返回了,就對返回進行處理(走catch)
  4. 如果用來處理同步,則和SyncBailHook效果一樣
  5. 如果處理tapSync,則遇到return true最終的callback不會執行
  6. 如果處理promise,則遇到rejcet(true),則直接進入catch

Q16:AsyncParallelBailHook如何實現?

這個AsyncParallelBailHook真真燒腦了好一會

class AsyncParallelBailHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapPromise(name,task){
    this.tasks.push(task);
  }
  promise(...args){
    const param =  args.slice(0,this.limit.length);
    const tasks = this.tasks.map(task=>{
      return new Promise((resolve,reject)=>{
        task(...param).then((data)=>{
          resolve(data);
        },(err)=>{
          err? reject(err):resolve();
        });
      })
    });
    return Promise.all(tasks)
  }
}
複製程式碼
  1. 正常情況下,promise.all中任意一個任務reject,就會進入統一的catch
  2. 但我們需要的是根據reject的值來判斷是否走如catch
  3. 所以我們在原有task外,再包一層promise
  4. 如果reject值為真,則執行reject
  5. 如果reject值為假,則執行resolve,就當什麼也沒發生

Q17:啥是AsyncSeriesHook?

前面講的是非同步並行,現在該說非同步序列了,例如小王,學完webpack才去學的react,你也不知道他什麼時候學完,但他學完一個就會告訴你一下,例:

const {AsyncSeriesHook} = require('tapable');
const FontEnd = new AsyncSeriesHook(['name']);
console.time('webpack');
console.time('react');
FontEnd.tapPromise('webpack',(name,cb)=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
      console.log(name+" get webpack ")
      console.timeEnd('webpack');
      resolve();
    }, 1000);
  })
});
FontEnd.tapPromise('react',(name,cb)=>{
  return new Promise((resolve)=>{
    setTimeout(() => {
      console.log(name+" get react ")
      console.timeEnd('react');
      resolve();
    }, 1000);
  })
});
FontEnd.start=(...args)=>{
  FontEnd.promise(...args).then(()=>{
    console.log("end");
  })
};
FontEnd.start('小王');
複製程式碼

上面程式碼執行結果:

小王 get webpack 
webpack: 1010.781ms
小王 get react 
react: 2016.598ms
end
複製程式碼
  1. 兩個非同步任務,變成了序列
  2. 從時間能夠得出,兩個1s的非同步的任務,序列後總時間變成了2s

總結:AsyncSeriesHook解決的問題是非同步序列,例如node的os.cpus()有限,可以把任務分批次執行,這樣對效能有保障

Q18:AsyncSeriesHook如何實現?

class AsyncSeriesHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapPromise(name,task){
    this.tasks.push(task);
  }
  promise(...args){
    const param =  args.slice(0,this.limit.length);
    const [first,...others] = this.tasks;
    return others.reduce((pre,next)=>{
      return pre.then(()=>next(...param))
    },first(...param))
  }
}
複製程式碼
  1. 實現核心就是promise序列
  2. 取出第一個任務,執行拿到promise例項,然後通過reduce遍歷

Q19:啥是AsyncSeriesBailHook?

還是前面的例子,如果小王學前端,學了webapck就徹底放棄了,那後面的react也就不用學了

const {AsyncSeriesBailHook} = require('tapable');
const FontEnd = new AsyncSeriesBailHook(['name']);
console.time('webpack');
console.time('react');
FontEnd.tapPromise('webpack',(name,cb)=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
      console.log(name+" get webpack ")
      console.timeEnd('webpack');
      reject('小王徹底放棄了');
    }, 1000);
  })
});
FontEnd.tapPromise('react',(name,cb)=>{
  return new Promise((resolve)=>{
    setTimeout(() => {
      console.log(name+" get react ")
      console.timeEnd('react');
      resolve();
    }, 1000);
  })
});
FontEnd.start=(...args)=>{
  FontEnd.promise(...args).then(()=>{
    console.log("end");
  }).catch((err)=>{
    console.log("err",err)
  })
};
FontEnd.start('小王');
複製程式碼

上面程式碼輸出:

小王 get webpack 
webpack: 1010.518ms
err 小王徹底放棄了
複製程式碼
  1. 上面的程式碼只執行到webpack
  2. AsyncSeriesBailHook,任務如果return,或者reject,則阻塞了

場景:主要是非同步序列,如果某一個任務執行的結果reject或者return,那麼後面的都將不再執行

Q20:AsyncSeriesBailHook如何實現?

class AsyncSeriesBailHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapPromise(name,task){
    this.tasks.push(task);
  }
  promise(...args){
    const param =  args.slice(0,this.limit.length);
    const [first,...others] = this.tasks;
    return new Promise((resolve,reject)=>{
      others.reduce((pre,next,index,arr)=>{
        return pre
          .then(()=>next(...param))
          .catch((err=>{
            arr.splice(index,arr.length-index);
            reject(err);
          })).then(()=>{
            (index+1 === arr.length) && resolve();
          })
      },first(...param))
    })
  }
}
複製程式碼

AsyncSeriesBailHook實現難度要高很多

  1. 首先在reduce外再包一層promise
  2. 當遇到任何一個子任務進入catch的時候,則將reduce的第四個引數arr切割,使其無法再向下進行,也就是停止reduce的繼續
  3. 同時所有promise後面再新增一個後置then,用來檢測是否全部執行完成
  4. 為什麼使用index+1,是因為後置then肯定是最後一個任務,但遍歷index還處於上一個下標,所以只要加1就好

Q21:啥是AsyncSeriesWaterfallHook?

SyncWaterFallHook前面已經瞭解過了,就是前一個執行完的結果會傳遞給下一個執行函式,和AsyncSeriesWaterfallHook的區別就是,一個是同步一個是非同步

具體來說,例如只有一本教材,小王學完,小張才能學

const FontEnd = new AsyncSeriesWaterfallHook(['name']);
FontEnd.tapAsync('webpack',(name,cb)=>{
    setTimeout(() => {
      console.log(name+" get webpack ")
      cb(null,'小李');
    }, 1000);
});
FontEnd.tapAsync('webpack',(name,cb)=>{
  setTimeout(() => {
    console.log(name+" get webpack ")
    cb(null,'小張');
  }, 1000);
});
FontEnd.tapAsync('webpack',(name,cb)=>{
  setTimeout(() => {
    console.log(name+" get webpack ")
    cb(null,'小紅');
  }, 1000);
});
FontEnd.start=(...args)=>{
  FontEnd.callAsync(...args,(data)=>{
    console.log("全學完了",)
  })
};
FontEnd.start('小王');
複製程式碼

上面程式碼,最終輸出:

小王 get webpack 
小李 get webpack 
小張 get webpack 
全學完了
複製程式碼

總結:這個的用法和SyncWaterFallHook的用法一致

Q22:AsyncSeriesWaterfallHook如何實現?

class AsyncSeriesWaterfallHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapAsync(name,task){
    this.tasks.push(task);
  }
  callAsync(...args){
    const param =  args.slice(0,this.limit.length);
    const finalCallBack = args.pop();
    let index = 0;
    const next = (err,data)=>{
      const task = this.tasks[index];
      if(!task)return finalCallBack();
      if(index === 0){
        task(...param,next)
      }else{
        task(data,next)
      }
      index++;
    }
    next();
  }
}
複製程式碼
  1. 主要是通過封裝一個回撥函式next
  2. 然後不斷呼叫任務佇列中的任務,呼叫的時候,再傳遞相同的回撥函式進去

prmise版本的實現如下:

class AsyncSeriesWaterfallHook {
  constructor(limit = []){
    this.limit= limit;
    this.tasks = [];
  }
  tapPromise(name,task){
    this.tasks.push(task);
  }
  promise(...args){
    const param =  args.slice(0,this.limit.length);
    const [first,...others] = this.tasks;
    return others.reduce((pre,next)=>{
      return pre.then((data)=>{
        return data?next(data):next(...param);
      })
    },first(...param))
  }
}
複製程式碼
  1. promise的實現要相對簡單一些
  2. 主要去看then方法中是否有內容,如果有的話,則傳遞個下一個函式,如果沒有,則用初始引數

總結

  1. tapable的各AsyncHook都同時支援tap、tapAsync、tapPromise
  2. tapable主要解決的是事件流轉的問題,各個Hook使用的場景各有不同
  3. tapable主要應用在webpack的各個生命週期中,具體的實踐還需要結合webpack原理去看

引用

tapable使用指南

相關文章