tapable是什么

95 阅读5分钟

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.tapSyncHook.call分别对应EventEmitter.onEventEmitter.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));
  }
}

小结

  1. SyncBailHook主要解决的问题是条件阻塞
  2. 当订阅事件符合某一判断时,不再执行下面的流程
  3. 应用场景,场景不断深入的场景,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);
  }
}

小结

  1. SyncWaterfallHook将前一个任务的执行结果,作为参数传递给后一个任务。
  2. 适用于任务逻辑之间存在依赖关系的场景。

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));
  }
}

小结

  1. 通过回调和计数器来记录异步任务的执行。
  2. 用于解决异步并行的任务,类似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);
  }
}

小结

  1. 通过向reject传入真值,进入catch处理来中断任务的执行。类比SyncBailHook的return true;
  2. 中断并不会阻止后续异步任务的执行,但最终的结束任务会被阻止。

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)
    );
  }
}

示例来自网上,并做了适当修剪。