webpack
最近针对webpack进行了一些系统的学习,将一些学习的基础知识点做成脑图,方便自己记忆,也同时给大家分享一下自己的学习过程,有遗漏补充的地方请各方大牛补充一二,其中包含了各地的砖头,🙏,阿弥陀佛,勿怪勿怪!!!!!!!!!
webpack之热更新原理
启动服务阶段(webpack-dev-server/lib/Server.js)
- 运行
npm run bin初始化一个webpack的实例,启动webpack的编译 addEnties函数,处理生成一个新的webpack的entry,便于生成websocket客户端代码:node_modules/webpack-dev-server/client/index.js以及热更新代码:webpack/hot/dev-server.js- 完成后可以让这两块代码能够被打包到本地
bundlejs,植入到浏览器中去,最后在浏览器运行的时候,可以找到 - 接下来是监听webpack阶段(下面统一总结)
- 调用
express生成一个静态文件能够使用的服务器,便于用ip来访问文件 - 创建server之后,紧接着会通过
ws或者socketjs创建一个websocket长链接
监听webpack阶段
-
调用
setUpHooks函数,对webpack编译完成阶段进行监听:done.tap('webpack-dev-server',function(stats){...}) -
等监听到webpack编译完成之后就调用回调:
this._sendStats(this.sockets,this.getStats(stats)) -
_sendStats通过websocket,写入hash和ok两个指令,向websocket client发送,hash向浏览器端发送最新的hash(这是一个很重要的东西),ok指令通知客户端webpack编译完成了。 -
接下来通过
setupDevMiddleware方法监听文件的变化,,在这里是利用webpack-dev-middleware这个库来进行文件的操作(编译,输出以及监听),以便让webpack-dev-server功能分离出来,专心做启动服务和准备工作 -
webpack-dev-middle调用compiler.watch方法对本地文件的变化进行监听(主要通过文件的改变时间,这就是为什么文件发生改变,就能继续启动webpack的编译),同时将编译打包完的代码通过memory-fs库的setFs(context,compiler)写入内存中,这就是为什么dist目录找不到热更新的代码的原因
浏览器收到热更新通知的阶段
- 利用
webpack-dev-server/client/index.js内的websocket 服务器可以收到hash和ok指令 - 将最新的
hash值保存在currentHash中,ok方法在内部调用了reloadApp(options,status) reloadApp利用webpack/hot/emitter的emit('webpackHotUpdate')去触发webpackHotUpdate- 这个
webpackHotUpdate函数的注册在那里呢?答案就是node_modules/webpack/hot/dev-server.js'一开始和webpack client函数一起写进浏览器的。在一步中,webpackHotUpdate的回调函数将currentHash存放在了lastHash中,同时调用了check(),这个check方法由module.hot.check提供,这个module.hot.check是那里出来的呢?答案就是:hotModuleReplacementPlugin
HotModuleReplacement检查代码更新阶段
执行了module.hot.check
- 利用之前得到的最新的
hash,调用hotDownloadManifest请求hash.hot-update.json,获取到要更新的代码的信息,并进入到热更新准备 - 然后调用
hotDownloadUpdateChunk发送hash.hot-update.js,通过JSONP方式,获取到最新的代码,然后根据JSONP的特性,直接执行了最新的代码块的代码中的webpackHotUpdate方法
代码替换阶段(window['webpackHotUpdate'])
- 将要更新的模块
moreModules保存到全局对象hotUpdate,然后调用 **hotApply**开始进行模块的替换 - 删除过期模块,并将模块
id存储下来,以防止更新失败,可以进行回退 - 将新的模块添加到
modules里面 - 要知道webpack打包后要执行代码都是通过
bundle里面的__webpack__require__执行,在hotApply的最后,调用__webpack__require__,将moduleId传入,执行modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
结束阶段
- 然后执行了
module.hot.check的回调,为了容错执行window.location.reload(),这时候结束了整个过程.打印出[HMR] App is up to date
以下地址包含了具体源代码的分析(砖🧱)
webpack运行流程原理
[学习中。。。]
plugin原理
plugin是webpack中非常重要和功能,plugin提供了loaders无法做到的事,包括环境变量的增加,代码压缩和打包的优化等等。
一个plugin应该至少具有以下内容
- 一个JS的类或者一个函数
- 在原型上有一个apply的函数,负责在webpack在编译的时候,执行该函数
- apply需要有一个compiler的参数,便于plugin能获取到编译过程中的一些资源
- 使用compiler提供的hook,注册一个回调函数(想要知道具体可以看文章内的关于Tapable的介绍)
- 必须调用webpack回调函数提供的 callback,即回调的第二个参数,必须在回调代码执行的最后调用,否则下一个plugin无法继续进行
- 绑定的回调函数除了有callback这个参数,还需要一个非常重要的compilation,负责当前的模块资源,编译生成资源,变化的文件,以及跟踪以来的状态信息
总结
继承自tapable的两个实例:compiler和compilation,webpack为了区分两者的工作范围,compiler主要负责的是源码编译,而compilation负责文件输出
自定义实现一个简单的plugin
当然plugin没有loader那么方便,可以直接脱离webpack执行(loader可以使用loader-runner),所以要创建一个plugin需要依赖webpack的plugins数组
// webpack.dev.js
const simpleWebpackPlugin = require('../plugins/simple-webpack-plugin.js')
plugins: [
new simpleWebpackPlugin({name: '新建Plugin'})
],
module.exports = class SimpleWebpackPlugin{
constructor(options) {
this.options = options;
}
apply(compiler) {
console.log(this.options)
const options = this.options
compiler.hooks.emit.tapAsync('ImgPlugin', (compilation, callback) => {
compilation.assets['readme.txt'] = {
source:function(){
return options.name
},
size:function(){
return 6
}
}
callback()
})
}
}
在这里我们最后使用complation的生成文件的过程,最后可以在打包的dist文件中生成一个readme.text文件,并被写入:新建Plugin
loaders的学习
loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,比如编译,压缩等等,并最终打包到指定的文件中,处理一个文件能够使用多个loader,loader的执行顺序和配置中的顺序是相反的(从右往左执行)
原因:是因为
webpack采用的是compose,类似于a(b)的时候,它会从从右到左的:b->a的执行 也就是说loader的执行接收前一个执行的loader的返回值作为参数,最后执行的loader会 返回此模块的此模块的javascript源码
loader就是一个简单的函数- 一个
loader的职责是单一的,只能进行一种转化,如果一个源文件需要多个转化才能正常使用,那就要借助多个loader,如sass和less文件的执行
loader-utils
官方提供的可以通过loaderUtils.getOptions(this)来获取webpack的配置参数,然后进行自己的处理
loader的异常处理和结果的回调处理
- 可以直接通过
throw来抛出错误 - 通过
this.callback传递错误(同步处理方式)
this.callback({error,content,sourceMap,meta})
// error可以为null,表示没有错误
this.async来处理异步loader
loader缓存的开启
webpack默认开启了loader的缓存
可以使用this.cachable(false)关掉缓存
pitch钩子
在loader文件中,pitch钩子的注册,可以让这个函数在所有的loader前被调用
data设置的值,可以在loader里面获取到,在每个loader里面都可以通过this.data获取到这些值
module.exports.pitch = (remaining, preceding, data) => {
data.value = "bajiu"
}
module.export = function(data){
console.log('data',this.data.value) // bajiu
}
手动实现一个loader
tapable学习
webpack是基于事件驱动,类似于node的event emitter:注册事件,然后触发事件,但是Tapable要比Event Emitter更加的强大,webpack的整个工作流程就是将所有的插件串联起来,而保证这一切能够实现的的核心就是Tapable
Tapable的核心
- 负责编译的
Compiler - 负责创建
bundles的complation
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
All Hook constructors take one optional argument, which is a list of argument names as strings.
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
// ["arg1","arg2","arg3"] 控制触发事件,最多能传入多少个参数
Tapable主要钩子hook
主要有以下几种钩子
- 大体分为同步和异步的钩子
- 异步又分为:异步串行钩子和异步并行钩子
- 而根据事件终止条件的不同,可以分为
Bail/waterfall/Loop
| 名称 | 注册事件的方法 | 回调时间的方法 | 作用 |
|---|---|---|---|
| Hook | tap,tapAsync,tapPromise | --- | 钩子基类 |
| SyncHook | tap | call | 同步钩子,顺序执行 |
| SyncBailHook | tap | call | 同步钩子,只要执行的监听函数handler有返回值不为undefined,剩余的handler就不会执行 |
| SyncLoopHook | tap | call | 同步循环钩子,只要执行的监听函数handler有返回值为true,那么就会一直执行这个钩子,如果返回undefined则表示会退出循环 |
| SyncWaterfallHook | tap | call | 同步流水钩子,上一个监听函数handler的返回值作为下一个handler的参数 |
| AsyncParallelBailHook | tap,tapAsync,tapPromise | call,callAsync,promise | 异步并行熔断钩子,监听函数handler并行触发,但是跟内部调用逻辑的快慢有关联 |
| AsyncParallelHook | tap,tapAsync,tapPromise | call,callAsync,promise | 异步并行钩子,不关心返回值 |
| AsyncSeriesBailHook | tap,tapAsync,tapPromise | call,callAsync,promise | 异步串行钩子熔断钩子,handler串行触发,但是跟handler内部调用回调的逻辑有关 |
| AsyncSeriesHook | tap,tapAsync,tapPromise | call,callAsync,promise | 异步串行钩子,handler串行触发 |
源码分析及原理分析
任何不拿源码分析的文章都是太简单了,接下来所有的分析只针对于
SyncHook来分析的,因为大部分过程是一致的
在package.json文件的描述中可以看到Just a little module for plugins.
入口文件:"main": "lib/index.js"
从源码分析可以发现:
- 存在两个基础类:
Hook以及HookCodeFactory工厂类 Hook有一下几个子类:SyncHook,SyncBailHook,SyncLoopHook,SyncWaterfallHook等。在父类Hook上可以发现两个最为主要的方法:tap和call,webpack围绕这两个方法,构建出一个基于plugin的工作方式HookCodeFactory产生了对应于Hook的几个子工厂类:SyncHookCodeFactory,SyncBailHOokCodeFactory等- 通过
const factory = new SyncHookCodeFactory(),在生成对应的工厂实例:factory - 然后在
SyncHook的方法compile,调用了factory.setUp以及返回了factory.create(options) - 然后根据
options内部的type,通过Function构建出可执行的函数,最后在调用call的时候被调用compile
tap的实现
在tapable的使用中,tap函数是一个非常重要的内容。
const {
SyncHook
} = require('tapable')
const hook = new SyncHook(['aa','bb'])
hook.tap('a',function(arg,arg1,arg2){
console.log('arg1',arg,arg1,arg2)
})
hook.tap({name: 'b',before: 'a'},function(arg,arg1,arg2){
console.log('arg2',arg,arg1,arg2)
})
hook.tap({name: 'c',stage:-1},function(arg,arg1,arg2){
console.log('arg3',arg,arg1,arg2)
})
// stage的大小决定了它执行的顺序
// before决定了这个回调函数要在那个回调函数前执行
hook.call('b','c','d')
tap函数的核心概念就是:将所有绑定的回调函数,都存放在taps这么样一个数组里面。
在上文提到了,所有的Hook都继承与唯一的父类Hook,既然在SyncHook等并没有发现tap函数的重载或者重写,那么就需要在父类Hook找到tap方法
// lib/Hook.js
tap(options, fn) {
options = this._runRegisterInterceptors(options);
this._insert(options);
}
_insert函数
_insert(item) {
// 在_resetCompilation,将call,promise,callSync等
this._resetCompilation();
let before;
if (typeof item.before === "string") before = new Set([item.before]);
else if (Array.isArray(item.before)) {
before = new Set(item.before);
}
let stage = 0;
if (typeof item.stage === "number") stage = item.stage;
let i = this.taps.length;
while (i > 0) {
i--;
const x = this.taps[i];
this.taps[i + 1] = x;
const xStage = x.stage || 0;
if (before) {
if (before.has(x.name)) {
before.delete(x.name);
continue;
}
if (before.size > 0) {
continue;
}
}
if (xStage > stage) {
continue;
}
i++;
break;
}
this.taps[i] = item;
}
// _resetCompilation
_resetCompilation() {
this.call = this._call;
this.callAsync = this._callAsync;
this.promise = this._promise;
}
_insert根据before和stage确定这个回调函数在taps位置。
call的实现
同样的,call跟tap一样是在父类Hook中实现
// lib/Hook.js
this.call = this._call
Object.defineProperties(Hook.prototype, {
_call: {
value: createCompileDelegate("call", "sync"),
configurable: true,
writable: true
},
_promise: {
value: createCompileDelegate("promise", "promise"),
configurable: true,
writable: true
},
_callAsync: {
value: createCompileDelegate("callAsync", "async"),
configurable: true,
writable: true
}
});
使用了Object.defineProperties创建了_call方法,之后调用_call也就是相当于调用了value属性的createCompileDelegate,然后最终调用了_createCall
this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
但是在_createCall调用了this.compile,在父类Hook中,compile是一个空的实现,所以得看子类hook得实现
compile(options) {
// 在call的时候被调用 _createCall taps interceptors _args type
factory.setup(this, options);
return factory.create(options);
}
然后再看HookCodeFactory中,生成Function的部分代码
case "sync":
fn = new Function(
this.args(),
'"use strict";\n' +
this.header() +
this.content({
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
break;
来看看Function构造函数的语法: new Function(arg1, arg2........argN, body);
可以知道
this.args()生成的是所有的参数,然后紧接着的是body
可以看到,主要的是this.header,this.content的内容
来看看最后生成的fn是什么
console.log(fn)
// [Function: anonymous] fn
一个闭包
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(aa, bb);
var _fn1 = _x[1];
_fn1(aa, bb);
var _fn2 = _x[2];
_fn2(aa, bb);
this._x就是之前的taps数组,这个闭包功能就是将taps里面的回调函数拿出来执行aa和bb是一开始初始化SyncHook传入的参数
疑问
- 为什么源码要采用这样一个一个函数拿出来执行的过程,而不直接采用循环来执行这些回调函数?
content
在生成的过程中
this.header负责处理全局的变量this.args()负责处理call过程中的参数this.content()负责处理核心的流程
而this.content是由每一个子类工厂实现的,如:SyncHookCodeFactory
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
在这里调用了callTapsSeries,在这个函数里面最核心的内容就是
const content = this.callTap(i, {
onError: error => onError(i, error, done, doneBreak),
onResult:
onResult &&
(result => {
return onResult(i, result, done, doneBreak);
}),
onDone: !onResult && done,
rethrowIfPossible:
rethrowIfPossible && (firstAsync < 0 || i < firstAsync)
});
来大概看一下超长待机callTap的内容
let code = "";
let hasTapCached = false;
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.tap) {
if (!hasTapCached) {
code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
hasTapCached = true;
}
code += `${this.getInterceptor(i)}.tap(${
interceptor.context ? "_context, " : ""
}_tap${tapIndex});\n`;
}
}
code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
const tap = this.options.taps[tapIndex];
switch (tap.type) {
case "sync":
if (!rethrowIfPossible) {
code += `var _hasError${tapIndex} = false;\n`;
code += "try {\n";
}
if (onResult) {
code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
} else {
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
}
if (!rethrowIfPossible) {
code += "} catch(_err) {\n";
code += `_hasError${tapIndex} = true;\n`;
code += onError("_err");
code += "}\n";
code += `if(!_hasError${tapIndex}) {\n`;
}
if (onResult) {
code += onResult(`_result${tapIndex}`);
}
if (onDone) {
code += onDone();
}
if (!rethrowIfPossible) {
code += "}\n";
}
break;
case "async":
let cbCode = "";
if (onResult) cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`;
else cbCode += `_err${tapIndex} => {\n`;
cbCode += `if(_err${tapIndex}) {\n`;
cbCode += onError(`_err${tapIndex}`);
cbCode += "} else {\n";
if (onResult) {
cbCode += onResult(`_result${tapIndex}`);
}
if (onDone) {
cbCode += onDone();
}
cbCode += "}\n";
cbCode += "}";
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined,
after: cbCode
})});\n`;
break;
case "promise":
code += `var _hasResult${tapIndex} = false;\n`;
code += `var _promise${tapIndex} = _fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
code += `if (!_promise${tapIndex} || !_promise${tapIndex}.then)\n`;
code += ` throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise${tapIndex} + ')');\n`;
code += `_promise${tapIndex}.then(_result${tapIndex} => {\n`;
code += `_hasResult${tapIndex} = true;\n`;
if (onResult) {
code += onResult(`_result${tapIndex}`);
}
if (onDone) {
code += onDone();
}
code += `}, _err${tapIndex} => {\n`;
code += `if(_hasResult${tapIndex}) throw _err${tapIndex};\n`;
code += onError(`_err${tapIndex}`);
code += "});\n";
break;
}
console.log(code,'code')
return code;
这代码有点长,直接分析太过空洞,所以结合生成的代码来看是比较合适的
工厂类创建的执行代码对比
syncHook
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(aa, bb);
var _fn1 = _x[1];
_fn1(aa, bb);
var _fn2 = _x[2];
_fn2(aa, bb);
可以看到没有任何限制,直接将taps里面所有的
SyncBailHook
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _result0 = _fn0(aa, bb);
if(_result0 !== undefined) {
return _result0;
;
} else {
var _fn1 = _x[1];
var _result1 = _fn1(aa, bb);
if(_result1 !== undefined) {
return _result1;
;
} else {
var _fn2 = _x[2];
var _result2 = _fn2(aa, bb);
if(_result2 !== undefined) {
return _result2;
;
} else {
}
}
}
可以看到当回调函数的返回值不为undefined的时候,就会停止向下执行,并且返回当前的回调函数的返回值
SyncLoopHook
"use strict";
var _context;
var _x = this._x;
var _loop;
do {
_loop = false;
var _fn0 = _x[0];
var _result0 = _fn0(aa, bb);
if(_result0 !== undefined) {
_loop = true;
} else {
var _fn1 = _x[1];
var _result1 = _fn1(aa, bb);
if(_result1 !== undefined) {
_loop = true;
} else {
var _fn2 = _x[2];
var _result2 = _fn2(aa, bb);
if(_result2 !== undefined) {
_loop = true;
} else {
if(!_loop) {
}
}
}
}
} while(_loop);
定义了一个为_loop的变量控制了do-while的执行,当回调函数的返回值不为undefined的时候,就会重复该回调函数的执行,直到返回undefined,才会执行下一个回调函数callback
SyncWaterfallHook
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _result0 = _fn0(aa, bb);
if(_result0 !== undefined) {
aa = _result0;
}
var _fn1 = _x[1];
var _result1 = _fn1(aa, bb);
if(_result1 !== undefined) {
aa = _result1;
}
var _fn2 = _x[2];
var _result2 = _fn2(aa, bb);
if(_result2 !== undefined) {
aa = _result2;
}
return aa;
可以看到当前SyncWaterfallHook如果,回调函数中的返回值不为undefined,那么这个返回值将成为下一个回调函数执行时的参数
接下来是异步hook的分析
demo代码:
const {
AsyncParallelHook
} = require('tapable')
const hook = new AsyncParallelHook(['aa'])
hook.tap('a',function(arg){
console.log('arg1',arg)
})
hook.tap({name: 'b',before: 'a'},function(arg){
console.log('arg2',arg)
})
hook.tap({name: 'c',stage:-1},function(arg){
console.log('arg3',arg)
})
hook.callAsync('b',function(data){
console.log('这是响应回调',data)
})
AsyncParallelHook
"use strict";
var _context;
var _x = this._x;
do {
var _counter = 3;
var _done = () => {
_callback();
};
if(_counter <= 0) break;
var _fn0 = _x[0];
var _hasError0 = false;
try {
_fn0(aa);
} catch(_err) {
_hasError0 = true;
if(_counter > 0) {
_callback(_err);
_counter = 0;
}
}
if(!_hasError0) {
if(--_counter === 0) _done();
}
if(_counter <= 0) break;
var _fn1 = _x[1];
var _hasError1 = false;
try {
_fn1(aa);
} catch(_err) {
_hasError1 = true;
if(_counter > 0) {
_callback(_err);
_counter = 0;
}
}
if(!_hasError1) {
if(--_counter === 0) _done();
}
if(_counter <= 0) break;
var _fn2 = _x[2];
var _hasError2 = false;
try {
_fn2(aa);
} catch(_err) {
_hasError2 = true;
if(_counter > 0) {
_callback(_err);
_counter = 0;
}
}
if(!_hasError2) {
if(--_counter === 0) _done();
}
} while(false);(--_counter === 0) _done();
}
_counter是表示绑定的回调函数的个数_callback代表调用的回调函数- 当执行时候报错了,同时当前执行的
_counter还存在的话,那么会执行回调,同时传入_err - 当最后一个函数执行完成,也会执行
_callback
AsyncParallelBailHook
"use strict";
var _context;
var _x = this._x;
var _results = new Array(3);
var _checkDone = () => {
for(var i = 0; i < _results.length; i++) {
var item = _results[i];
if(item === undefined) return false;
if(item.result !== undefined) {
_callback(null, item.result);
return true;
}
if(item.error) {
_callback(item.error);
return true;
}
}
return false;
}
do {
var _counter = 3;
var _done = () => {
_callback();
};
if(_counter <= 0) break;
var _fn0 = _x[0];
var _hasError0 = false;
try {
var _result0 = _fn0(aa);
} catch(_err) {
_hasError0 = true;
if(_counter > 0) {
if(0 < _results.length && ((_results.length = 1), (_results[0] = { error: _err }), _checkDone())) {
_counter = 0;
} else {
if(--_counter === 0) _done();
}
}
}
if(!_hasError0) {
if(_counter > 0) {
if(0 < _results.length && (_result0 !== undefined && (_results.length = 1), (_results[0] = { result: _result0 }), _checkDone())) {
_counter = 0;
} else {
if(--_counter === 0) _done();
}
}
}
/**重复逻辑代码省略**/
} while(false);
_checkdone函数利用循环,只要注册的回调函数taps,里面有一个返回值不为undefined,那么就会执行_callback(注册callAsync注册的回调)- 执行错误或者执行完成的时候
AsyncSeriesHook
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _hasError0 = false;
try {
_fn0(aa);
} catch(_err) {
_hasError0 = true;
_callback(_err);
}
if(!_hasError0) {
var _fn1 = _x[1];
var _hasError1 = false;
try {
_fn1(aa);
} catch(_err) {
_hasError1 = true;
_callback(_err);
}
if(!_hasError1) {
var _fn2 = _x[2];
var _hasError2 = false;
try {
_fn2(aa);
} catch(_err) {
_hasError2 = true;
_callback(_err);
}
if(!_hasError2) {
_callback();
}
}
}
异步串行钩子
- 不会关注任何回调函数的返回值,只会在报错的时候或者执行了最后一个回调函数且不报错的时候,会执行
callAsync绑定的回调
AsyncSeriesBailHook
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _hasError0 = false;
try {
var _result0 = _fn0(aa);
} catch(_err) {
_hasError0 = true;
_callback(_err);
}
if(!_hasError0) {
if(_result0 !== undefined) {
_callback(null, _result0);
;
} else {
var _fn1 = _x[1];
var _hasError1 = false;
try {
var _result1 = _fn1(aa);
} catch(_err) {
_hasError1 = true;
_callback(_err);
}
if(!_hasError1) {
if(_result1 !== undefined) {
_callback(null, _result1);
;
} else {
var _fn2 = _x[2];
var _hasError2 = false;
try {
var _result2 = _fn2(aa);
} catch(_err) {
_hasError2 = true;
_callback(_err);
}
if(!_hasError2) {
if(_result2 !== undefined) {
_callback(null, _result2);
;
} else {
_callback();
}
}
}
}
}
}
- 返回值不为
undefined会直接触发tapAsync的参数
AsyncSeriesWaterfallHook
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _hasError0 = false;
try {
var _result0 = _fn0(aa);
} catch(_err) {
_hasError0 = true;
_callback(_err);
}
if(!_hasError0) {
if(_result0 !== undefined) {
aa = _result0;
}
var _fn1 = _x[1];
var _hasError1 = false;
try {
var _result1 = _fn1(aa);
} catch(_err) {
_hasError1 = true;
_callback(_err);
}
if(!_hasError1) {
if(_result1 !== undefined) {
aa = _result1;
}
var _fn2 = _x[2];
var _hasError2 = false;
try {
var _result2 = _fn2(aa);
} catch(_err) {
_hasError2 = true;
_callback(_err);
}
if(!_hasError2) {
if(_result2 !== undefined) {
aa = _result2;
}
_callback(null, aa);
}
}
}
- 下一个回调函数接收上一个回调函数的返回值(返回值不为
undefined)
总结
| 钩子 | 执行条件 |
|---|---|
| syncHook | 直接将taps里面所有的回调函数执行了 |
| SyncBailHook | 当回调函数的返回值不为undefined的时候,就会停止向下执行,并且返回当前的回调函数的返回值 |
| SyncLoopHook | 当回调函数的返回值不为undefined的时候,就会重复该回调函数的执行,直到返回undefined,才会执行下一个回调函数callback |
| SyncWaterfallHook | 回调函数中的返回值不为undefined,那么这个返回值将成为下一个回调函数执行时的参数 |
| AsyncParallelHook | 将taps里面的回调函数按照顺序执行,并不关心返回值 |
| AsyncParalleBailHook | 里面有一个返回值不为undefined,那么就会执行_callback(注册callAsync注册的回调) |
| AsyncSeriesHook | 不会关注任何回调函数的返回值 |
| AsyncSeriesBailHook | 返回值不为undefined会直接触发tapAsync的参数 |
| AsyncSeriesWaterfallHook | 下一个回调函数接收上一个回调函数的返回值(返回值不为undefined) |
个人最近学习关于原理方面的知识,有文章介绍的大佬吗?望推荐!!!