tapable是一个类似事件发射器EventEmitter的库,广泛应用于webpack中。相比传统事件发射器,它的功能更丰富。
进阶之路
SyncHook
示例
const FrontEnd = new SyncHook(['name']);// 添加参数约定
FrontEnd.tap('webpack', namea => {
console.log(name + ' get webpack');
});
FrontEnd.tap('react', name => {
console.log(name + ' get react');
});
FrontEnd.start = name => {
FrontEnd.call(name);
};
FrontEnd.start('xiaoming');
打印显示:
xiaoming get webpackxiaoming get react
实现
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));
}
}
小结
- 类似事件发射器,
SyncHook.tap和SyncHook.call分别对应EventEmitter.on和EventEmitter.fire。 - 用于一般的事件同步。
SyncBailHook
在SyncHook的基础上添加了熔断机制。如果前面的事件给了中断信号,则后面的事件不再执行。
示例
思路:事件通过返回真值告诉后面的事件不需要再执行了。
const FrontEnd = new SyncBailHook(['name']);
FrontEnd.tap('webpack', name => {
console.log(name + ' get webpack ');
return '学不动了啊!';
});
FrontEnd.tap('react', name => {
console.log(name + ' get react');
});
FrontEnd.start = (...args) => {
FrontEnd.call(...args);
};
FrontEnd.start('xiaoming');
显示:
xiaoming get webpack
实现
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));
}
}
小结
- SyncBailHook主要解决的问题是条件阻塞
- 当订阅事件符合某一判断时,不再执行下面的流程
- 应用场景,场景不断深入的场景,a、a+b、a+b+c、a+b+c+d这种场景
SyncWaterfallHook
事件流中,有时候不仅仅想要告诉后面的事件是否继续执行,甚至想要传递数据给后面的流程。
示例
思路:将前一个函数的执行结果传递给后面的函数。
const FrontEnd = new SyncWaterfallHook(["name"]);
FrontEnd.tap("webpack", (name) => {
console.log(name + " get webpack ");
return "学完webpack了,该学react了";
});
FrontEnd.tap("react", (name) => {
console.log(name + " get react");
});
FrontEnd.start = (...args) => {
FrontEnd.call(...args);
};
FrontEnd.start("xiaoming");
显示:
xiaoming get webpack
学完webpack了,该学react了 get react
实现
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) => next(pre), ret);
}
}
小结
- SyncWaterfallHook将前一个任务的执行结果,作为参数传递给后一个任务。
- 适用于任务逻辑之间存在依赖关系的场景。
SyncLoopHook
有时候任务需要执行不止一次,然后再进入下一个任务。
示例
思路:执行函数时,通过返回的信号判断是否结束重复执行。
const FrontEnd = new SyncLoopHook(["name"]);
let num = 0;
FrontEnd.tap("webpack", (name) => {
console.log(name + " get webpack ");
return ++num === 3 ? undefined : "再学一次";
});
FrontEnd.tap("react", (name) => {
console.log(name + " get react");
});
FrontEnd.start = (...args) => {
FrontEnd.call(...args);
};
FrontEnd.start("xiaoming");
显示
xiaoming get webpack
xiaoming get webpack
xiaoming get webpack
xiaoming get react
实现
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++;
}
}
}
}
小结
适合需要循环执行任务的场景。比较少见。
AsyncParallelHook
同步的处理方式有了,还有更多场景可能是需要处理异步任务。
示例
思路:使用回调函数。
const FrontEnd = new AsyncParallelHook(["name"]);
FrontEnd.tapAsync("webpack", (name, cb) => {
setTimeout(() => {
console.log(name + " get webpack ");
cb();
}, 1000);
});
FrontEnd.tapAsync("react", (name, cb) => {
setTimeout(() => {
console.log(name + " get react");
cb();
}, 1000);
});
FrontEnd.start = (...args) => {
FrontEnd.callAsync(...args, () => {
console.log("end");
});
};
FrontEnd.start("小王");
显示:
小王 get webpack
小王 get reactend
实现
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));
}
}
小结
- 通过回调和计数器来记录异步任务的执行。
- 用于解决异步并行的任务,类似Promise.all。
AsyncParallelHook - promise版
示例
const FrontEnd = new AsyncParallelHook(["name"]);
FrontEnd.tapPromise(
"webpack",
(name) =>
new Promise((resolve) => {
setTimeout(() => {
console.log(name + " get webpack ");
resolve();
}, 1000);
})
);
FrontEnd.tapPromise(
"react",
(name) =>
new Promise((resolve) => {
setTimeout(() => {
console.log(name + " get react ");
resolve();
}, 1000);
})
);
FrontEnd.start = (...args) => {
FrontEnd.promise(...args).then(() => {
console.log("end");
});
};
FrontEnd.start("小王");
显示:
小王 get webpack
小王 get react end
实现
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);
}
}
小结
同步流的方式实现异步并行任务的处理,相对更容易理解一些。
AsyncParallelBailHook
异步并行任务加上熔断机制功能。
示例
思路:通过Promise的reject来实现。
const FrontEnd = new AsyncParallelBailHook(["name"]);
FrontEnd.tapPromise(
"webpack",
(name) =>
new Promise((resolve, reject) => {
setTimeout(() => {
console.log(name + " get webpack ");
reject("小王学崩了!");
}, 1000);
})
);
FrontEnd.tapPromise(
"react",
(name, cb) =>
new Promise((resolve) => {
setTimeout(() => {
console.log(name + " get react ");
resolve();
}, 2000);
})
);
FrontEnd.start = (...args) => {
FrontEnd.promise(...args).then(
() => {
console.log("end");
},
(err) => {
console.log("听说:", err);
}
);
};
FrontEnd.start("小王");
显示
小王 get webpack
听说: 小王学崩了!
小王 get react
实现
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) =>
new Promise((resolve, reject) => {
task(...param).then(
(data) => {
resolve(data);
},
(err) => {
err ? reject(err) : resolve();
}
);
})
);
return Promise.all(tasks);
}
}
小结
- 通过向reject传入真值,进入catch处理来中断任务的执行。类比SyncBailHook的return true;
- 中断并不会阻止后续异步任务的执行,但最终的结束任务会被阻止。
AsyncSeriesHook
异步任务除了并行处理,还可能需要串行处理。
示例
思路:串行处理promise。
const FrontEnd = new AsyncSeriesHook(["name"]);
console.time("webpack");
console.time("react");
FrontEnd.tapPromise(
"webpack",
(name, cb) =>
new Promise((resolve, reject) => {
setTimeout(() => {
console.log(name + " get webpack ");
console.timeEnd("webpack");
resolve();
}, 1000);
})
);
FrontEnd.tapPromise(
"react",
(name, cb) =>
new Promise((resolve) => {
setTimeout(() => {
console.log(name + " get react ");
console.timeEnd("react");
resolve();
}, 1000);
})
);
FrontEnd.start = (...args) => {
FrontEnd.promise(...args).then(() => {
console.log("end");
});
};
FrontEnd.start("小王");
显示:
小王 get webpack
webpack: 1010.781ms
小王 get react
react: 2016.598ms
end
实现
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) => pre.then(() => next(...param)),
first(...param)
);
}
}
小结
1.通过promise的串行执行来实现异步任务的串行处理。
AsyncSeriesBailHook
串行的异步任务也需要中断机制。
示例
思路:串行promise加入reject中断机制。
const FrontEnd = new AsyncSeriesBailHook(["name"]);
console.time("webpack");
console.time("react");
FrontEnd.tapPromise(
"webpack",
(name, cb) =>
new Promise((resolve, reject) => {
setTimeout(() => {
console.log(name + " get webpack ");
console.timeEnd("webpack");
reject("小王彻底放弃了");
}, 1000);
})
);
FrontEnd.tapPromise(
"react",
(name, cb) =>
new Promise((resolve) => {
setTimeout(() => {
console.log(name + " get react ");
console.timeEnd("react");
resolve();
}, 1000);
})
);
FrontEnd.start = (...args) => {
FrontEnd.promise(...args)
.then(() => {
console.log("end");
})
.catch((err) => {
console.log("err", err);
});
};
FrontEnd.start("小王");
显示:
小王 get webpack
webpack: 1010.518ms
err 小王彻底放弃了
实现
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) =>
pre
.then(() => next(...param))
.catch((err) => {
arr.splice(index, arr.length - index);
reject(err);
})
.then(() => {
index + 1 === arr.length && resolve();
}),
first(...param)
);
});
}
}
小结
适用于异步串行任务中也需要中断的任务。
AsyncSeriesWaterfallHook
示例
const FrontEnd = new AsyncSeriesWaterfallHook(["name"]);
FrontEnd.tapAsync("webpack", (name, cb) => {
setTimeout(() => {
console.log(name + " get webpack ");
cb(null, "小李");
}, 1000);
});
FrontEnd.tapAsync("webpack", (name, cb) => {
setTimeout(() => {
console.log(name + " get webpack ");
cb(null, "小张");
}, 1000);
});
FrontEnd.tapAsync("webpack", (name, cb) => {
setTimeout(() => {
console.log(name + " get webpack ");
cb(null, "小红");
}, 1000);
});
FrontEnd.start = (...args) => {
FrontEnd.callAsync(...args, (data) => {
console.log("全学完了");
});
};
FrontEnd.start("小王");
显示:
小王 get webpack
小李 get webpack
小张 get webpack
全学完了
实现
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();
}
}
小结
AsyncSeriesWaterfallHook - promise版本
示例
const FrontEnd = new AsyncSeriesWaterfallHook(["name"]);
FrontEnd.tapPromise(
"webpack",
(name) =>
new Promise((resolve) => {
setTimeout(() => {
console.log(name + " get webpack ");
resolve("小李");
}, 1000);
})
);
FrontEnd.tapPromise(
"webpack",
(name) =>
new Promise((resolve) => {
setTimeout(() => {
console.log(name + " get webpack ");
resolve("小张");
}, 1000);
})
);
FrontEnd.tapPromise(
"webpack",
(name) =>
new Promise((resolve) => {
setTimeout(() => {
console.log(name + " get webpack ");
resolve("小红");
}, 1000);
})
);
FrontEnd.start = (...args) => {
FrontEnd.promise(...args).then((data) => {
console.log("全学完了");
});
};
FrontEnd.start("小王");
显示:
小王 get webpack
小李 get webpack
小张 get webpack
实现
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) => pre.then((data) => (data ? next(data) : next(...param))),
first(...param)
);
}
}
示例来自网上,并做了适当修剪。