近期研究webpack loader和plugin编写,发现涉及到tapable,闲来无事翻翻源码结合使用方法记录一下webpack
tapable提供了不少钩子(Hook classes)github
const {
SyncHook, // 同步钩子 从上到下顺序执行
SyncBailHook, // 同步早退钩子 从上到下顺序执行,遇到返回值不是undefined的注册函数时中止执行
SyncWaterfallHook, // 同步瀑布钩子 从上到下执行,依次将返回值传递给下一个函数
SyncLoopHook, // 同步循环钩子 从上到下执行,某个函数可能会执行好几遍,当返回值是undefined会继续执行下个函数
AsyncParallelHook, // 异步并发钩子 异步并行
AsyncParallelBailHook, // 异步并发可早退钩子 异步并行熔断
AsyncSeriesHook, // 异步顺序钩子 异步串行
AsyncSeriesBailHook, // 异步顺序可早退钩子 异步串行熔断
AsyncSeriesWaterfallHook // 异步顺序瀑布钩子 异步串行值传递【瀑布】
} = require("tapable");
复制代码
import { SyncHook } from '../table/lib';
const hook = new SyncHook(); // 建立钩子对象
hook.tap('logPlugin', () => console.log('注册了')); // tap方法注册钩子回调
hook.call(); // call方法调用钩子,打印出‘被勾了’三个字
复制代码
SyncBailHook就是根据每一步返回的值来决定要不要继续往下走,若是return了一个非undefined的值 那就不会往下走,注意 若是什么都不return 也至关于return了一个undefined。数组
import { SyncBailHook } from '../table/lib';
const hook = new SyncBailHook();
hook.tap('SyncBailHook1', () => console.log(`钩子1`));
hook.tap('SyncBailHook2', () => {console.log(`钩子2`) ; return 1});
hook.tap('SyncBailHook3', () => console.log(`钩子3`));
hook.call(); // 会打印‘钩子1’‘钩子2’‘钩子3’
复制代码
它的每一步都依赖上一步的执行结果,也就是上一步return的值就是下一步的参数。promise
import { SyncWaterfallHook } from '../table/lib';
const hook = new SyncWaterfallHook(["newSpeed"]);
hook.tap('SyncWaterfallHook1', (speed) => { console.log(`增长到${speed}`); return speed + 100; });
hook.tap('SyncWaterfallHook2', (speed) => { console.log(`增长到${speed}`); return speed + 50; });
hook.tap('SyncWaterfallHook3', (speed) => { console.log(`增长到${speed}`); });
hook.call(50); // 打印‘增长到50’‘增长到150’‘增长到200’
复制代码
SyncLoopHook是同步的循环钩子,它的插件若是返回一个非undefined。就会一直执行这个插件的回调函数,直到它返回undefined。markdown
import { SyncLoopHook } from '../table/lib';
let index = 0;
const hook = new SyncLoopHook();
hook.tap('startPlugin1', () => {
console.log(`执行`);
if (index < 5) {
index++;
return 1;
}
});
hook.tap('startPlugin2', () => {
console.log(`执行2`);
});
hook.call(); // 打印‘执行’6次,打印‘执行2’一次。
复制代码
当全部的异步任务执行结束后,再最终的回调中执行接下来的代码并发
import { AsyncParallelHook } from '../table/lib';
const hook = new AsyncParallelHook();
hook.tapAsync('calculateRoutesPlugin1', (callback) => {
setTimeout(() => {
console.log('异步事件1');
callback();
}, 1000);
});
hook.tapAsync('calculateRoutesPlugin2', (callback) => {
setTimeout(() => {
console.log('异步事件2');
callback();
}, 2000);
});
hook.callAsync(() => { console.log('最终的回调'); }); // 会在1s的时候打印‘异步事件1’。2s的时候打印‘异步事件2’。紧接着打印‘最终的回调’
复制代码
import { AsyncParallelBailHook } from '../table/lib';
const hook = new AsyncParallelBailHook();
hook.tapAsync('calculateRoutesPlugin1', (callback) => {
setTimeout(() => {
console.log('异步事件1');
callback(1);
}, 1000);
});
hook.tapAsync('calculateRoutesPlugin2', (callback) => {
setTimeout(() => {
console.log('异步事件2');
callback();
}, 2000);
});
hook.callAsync((result) => { console.log('最终的回调',result); }); // 会在1s的时候打印‘异步事件1’,紧接着打印‘最终的回调’,2s的时候打印‘异步事件2’。
复制代码
import { AsyncSeriesHook } from '../table/lib';
const hook = new AsyncSeriesHook();
hook.tapPromise('calculateRoutesPlugin1', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('异步事件1');
resolve();
}, 1000);
});
});
hook.tapPromise('calculateRoutesPlugin2', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('异步事件2');
resolve();
}, 2000);
});
});
hook.then(() => { console.log('最终的回调'); });
// 1s事后,打印异步事件1,再过2s(而不是到了第2s,而是到了第3s),打印异步事件2,再立马打印最终的回调。
复制代码
import { AsyncSeriesBailHook } from '../table/lib';
const hook = new AsyncSeriesBailHook();
hook.tapPromise('calculateRoutesPlugin1', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('异步事件1');
resolve(1);
}, 1000);
});
});
hook.tapPromise('calculateRoutesPlugin2', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('异步事件2');
resolve();
}, 2000);
});
});
hook.then(() => { console.log('最终的回调'); });
// 1s事后,打印异步事件1,立马打印最终的回调,不会再执行异步事件2了。
复制代码
import { AsyncSeriesWaterfallHook } from '../table/lib';
const hook = new AsyncSeriesWaterfallHook(['args']);
hook.tapPromise('calculateRoutesPlugin1', (result) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('异步事件1', result);
resolve(result+1);
}, 1000);
});
});
hook.tapPromise('calculateRoutesPlugin2', (result) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('异步事件2', result);
resolve(result+2);
}, 2000);
});
});
hook.promise(12).then((result) => { console.log('最终的回调' + result); });
// // 1s事后,打印异步事件1 12,再过2s打印异步事件2 13,而后立马打印最终的回调 15。
复制代码
tapable 源码使用工厂模式和模板模式,Hook.js 抽离了功能函数,便于其余钩子函数继承,HookCodeFactory.js则使用模板模式,根据钩子函数的不一样生成不一样的执行函数返回调用的_call()异步
注册过程async
tapable注册方式有三种: tap、tapAsync和tapPromise,但过程基本都同样,都会调用 _insert()
HOOK.js
_insert(item) {
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;
console.log(this.taps)
}
1. item 对象是根据注册方式的不一样生成不一样的对象:
tap: { type: "sync", fn: fn ,name: name}
tapAsync: { type: "async", fn: fn ,name: name}
tapPromise: { type: "promise", fn: fn ,name: name}
后面的函数执行都会按照注册的type类型来执行
2. _insert() 做用: 根据注册类型的不一样生成不一样的对象并存储在 taps[] 中
复制代码
调用过程
3.1tapable调用方式有三种: call、promise和callAsync,调用createCompileDelegate(), 传入相应的类型
Hook.js
function createCompileDelegate(name, type) {
return function lazyCompileHook(...args) {
this[name] = this._createCall(type);
console.log(this[name])
return this[name](...args);
};
}
.....
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
....
_createCall() 函数将参数传给各个钩子的compile()函数,用来生成回调函数
复制代码
3.2各个钩子函数都会继承两个函数:
a. conent() 根据钩子函数的不一样生成不一样的执行函数即 _call()
b. compile(), 此函数会调用 HookCodeFactory.js 的两个函数 setup() 和 create()
setup(instance, options) {
instance._x = options.taps.map(t => t.fn);
}
// 将 optiuon数组保存的注册的函数过滤出来赋值给当前钩子函数的 ._X
create(options) {
this.init(options);
switch (this.options.type) {
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;
.....
}
this.deinit();
return fn;
}
init(options) {
this.options = options;
this._args = options.args.slice();
}
1. init()函数
这个函数很简单,但注意 _args 这个数组的由来, 他是lazyCompileHook()传入的参数,即 call、promise和callAsync 这三个回调函数传入的参数
2.create 根据注册钩子的类型调用各个钩子函数的 content()生成执行函数 fn
也就是说咱们调用(call())的时候实际上是执行下面这样一个函数,
anonymous() {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0();
var _fn1 = _x[1]; // 多个注册函数会生成多个
_fn1();
}
复制代码
//content函数
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
// 生成的执行函数
anonymous() {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0();
var _fn1 = _x[1];
_fn1();
}
复制代码
SyncHook 钩子生成多个 fn()函数,call()调用的时候挨个执行,比较简单
前面介绍使用方法时说过此钩子根据返回值判断是否继续执行,它的内部构造与 SyncHook稍有不一样:
// content 函数
content({ onError, onResult, resultReturns, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onResult: (i, result, next) =>
`if(${result} !== undefined) {\n${onResult(
result
)};\n} else {\n${next()}}\n`,
resultReturns,
onDone,
rethrowIfPossible
});
}
//生成的执行函数
(function anonymous() {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _result0 = _fn0();
if(_result0 !== undefined) { // 递归生成判断条件
return _result0;
} else {
var _fn1 = _x[1];
var _result1 = _fn1();
if(_result1 !== undefined) {
return _result1;
} else {
var _fn2 = _x[2];
var _result2 = _fn2();
if(_result2 !== undefined) {
return _result2;
} else {}
}
}
})
复制代码
SyncBailHook 的 content函数 的 onResult 多一个判断条件,判断上一个钩子函数的执行结果来决定是否继续执行
//content函数
content({ onError, onResult, resultReturns, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onResult: (i, result, next) => {
let code = "";
code += `if(${result} !== undefined) {\n`;
code += `${this._args[0]} = ${result};\n`; //与SyncBailHook 相比多加赋值的操做
code += `}\n`;
code += next();
return code;
},
onDone: () => onResult(this._args[0]),
doneReturns: resultReturns,
rethrowIfPossible
});
}
//生成的函数
(function anonymous(newSpeed) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _result0 = _fn0(newSpeed);
if(_result0 !== undefined) {
newSpeed = _result0;
}
var _fn1 = _x[1];
var _result1 = _fn1(newSpeed);
if(_result1 !== undefined) {
newSpeed = _result1;
}
var _fn2 = _x[2];
var _result2 = _fn2(newSpeed);
if(_result2 !== undefined) {
newSpeed = _result2;
}
return newSpeed;
})
复制代码
SyncWaterfallHook的content 的onResult 与SyncBailHook相比多赋值的操做,判断是否有返回值,若是有返回值就将返回值传给下一个执行函数
// content
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsLooping({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
//生成的函数
(function anonymous() {
"use strict";
var _context;
var _x = this._x;
var _loop;
do {
_loop = false;
var _fn0 = _x[0];
var _result0 = _fn0();
if(_result0 !== undefined) { //根据返回值判断是否继续执行
_loop = true;
} else {
var _fn1 = _x[1];
var _result1 = _fn1();
if(_result1 !== undefined) {
_loop = true;
} else {
if(!_loop) {}
}
}
} while(_loop);
})
callTapsLooping({ onError, onDone, rethrowIfPossible }) {
if (this.options.taps.length === 0) return onDone();
const syncOnly = this.options.taps.every(t => t.type === "sync");
let code = "";
if (!syncOnly) {
code += "var _looper = () => {\n";
code += "var _loopAsync = false;\n";
}
code += "var _loop;\n";
code += "do {\n";
code += "_loop = false;\n";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.loop) {
code += `${this.getInterceptor(i)}.loop(${this.args({
before: interceptor.context ? "_context" : undefined
})});\n`;
}
}
code += this.callTapsSeries({
onError,
onResult: (i, result, next, doneBreak) => {
let code = "";
code += `if(${result} !== undefined) {\n`;
code += "_loop = true;\n";
if (!syncOnly) code += "if(_loopAsync) _looper();\n";
code += doneBreak(true);
code += `} else {\n`;
code += next();
code += `}\n`;
return code;
},
....
}
复制代码
注意 SyncLoopHook 的content 函数与上面三个不一样,SyncLoopHook 的content 调用的是 callTapsLooping()函数,而其余的同步钩子调用的是 callTapsSeries(),
// content函数
content({ onError, onDone }) {
return this.callTapsParallel({
onError: (i, err, done, doneBreak) => onError(err) + doneBreak(true),
onDone
});
}
//生成的函数
(function anonymous(_callback) {
"use strict";
var _context;
var _x = this._x;
do {
var _counter = 2;
var _done = () => {
_callback();
};
if(_counter <= 0) break;
var _fn0 = _x[0];
_fn0(_err0 => {
if(_err0) { //若是有参数传入
if(_counter > 0) {
_callback(_err0);
_counter = 0;
}
} else {
if(--_counter === 0) _done();
}
});
if(_counter <= 0) break;
var _fn1 = _x[1];
_fn1(_err1 => {
if(_err1) {
if(_counter > 0) {
_callback(_err1);
_counter = 0;
}
} else {
if(--_counter === 0) _done();
}
});
} while(false);
})
复制代码
根据AsyncParallelHook 钩子生成的函数咱们能够看出他是判断err 有值时会走onError,执行 callback(),若是没有任何参数则当 _counter减为0即最后一个注册函数时执行callback()
// content
content({ onError, onResult, onDone }) {
let code = "";
code += `var _results = new Array(${this.options.taps.length});\n`;
code += "var _checkDone = () => {\n";
code += "for(var i = 0; i < _results.length; i++) {\n";
code += "var item = _results[i];\n";
code += "if(item === undefined) return false;\n";
code += "if(item.result !== undefined) {\n";
code += onResult("item.result");
code += "return true;\n";
code += "}\n";
code += "if(item.error) {\n";
code += onError("item.error");
code += "return true;\n";
code += "}\n";
code += "}\n";
code += "return false;\n";
code += "}\n";
code += this.callTapsParallel({
onError: (i, err, done, doneBreak) => {
let code = "";
code += `if(${i} < _results.length && ((_results.length = ${i +
1}), (_results[${i}] = { error: ${err} }), _checkDone())) {\n`;
code += doneBreak(true);
code += "} else {\n";
code += done();
code += "}\n";
return code;
},
onResult: (i, result, done, doneBreak) => {
let code = "";
code += `if(${i} < _results.length && (${result} !== undefined && (_results.length = ${i +
1}), (_results[${i}] = { result: ${result} }), _checkDone())) {\n`;
code += doneBreak(true);
code += "} else {\n";
code += done();
code += "}\n";
return code;
},
onTap: (i, run, done, doneBreak) => {
let code = "";
if (i > 0) {
code += `if(${i} >= _results.length) {\n`;
code += done();
code += "} else {\n";
}
code += run();
if (i > 0) code += "}\n";
return code;
},
onDone
});
return code;
}
//生成的函数
(function anonymous(_callback
) {
"use strict";
var _context;
var _x = this._x;
var _results = new Array(2);
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 = 2;
var _done = () => {
_callback();
};
if(_counter <= 0) break;
var _fn0 = _x[0];
_fn0((_err0, _result0) => {
if(_err0) {
if(_counter > 0) {
if(0 < _results.length && ((_results.length = 1), (_results[0] = { error: _err0 }), _checkDone())) {
_counter = 0;
} else {
if(--_counter === 0) _done();
}
}
} else {
if(_counter > 0) {
if(0 < _results.length && (_result0 !== undefined && (_results.length = 1), (_results[0] = { result: _result0 }), _checkDone())) {
_counter = 0;
} else {
if(--_counter === 0) _done();
}
}
}
});
if(_counter <= 0) break;
if(1 >= _results.length) {
if(--_counter === 0) _done();
} else {
var _fn1 = _x[1];
_fn1((_err1, _result1) => {
console.log('_err',_err1)
if(_err1) {
if(_counter > 0) {
if(1 < _results.length && ((_results.length = 2), (_results[1] = { error: _err1 }), _checkDone())) {
_counter = 0;
} else {
if(--_counter === 0) _done();
}
}
} else {
if(_counter > 0) {
if(1 < _results.length && (_result1 !== undefined && (_results.length = 2), (_results[1] = { result: _result1 }), _checkDone())) {
_counter = 0;
} else {
if(--_counter === 0) _done();
}
}
}
});
}
} while(false);
})
复制代码
网上有文章说这个钩子在第一个注册的插件执行完毕熔断,但好像不是这样,这个函数有个意思的地方,当err有参数传入时走的是 onError,没有参数走的是onResult
callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
....
switch (tap.type) {
case "sync":
case "async":
let cbCode = "";
if (onResult) cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`;
else cbCode += `_err${tapIndex} => {\n`;
cbCode += `console.log('_err',_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":
.....
}
return code;
}
这里判断err即有参数传入时执行的是onError,因此注册以后传入参数时其实执行的是_checkDone()循环判断_results的error有值时执行callback()
if(_counter > 0) {
if(0 < _results.length && ((_results.length = 1), (_results[0] = { error: _err0 }), _checkDone())) {
_counter = 0;
} else {
if(--_counter === 0) _done();
}
}
复制代码
//content
content({ onError, onDone }) {
return this.callTapsSeries({
onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
onDone
});
}
//生成的函数
(function anonymous(
) {
"use strict";
return new Promise((_resolve, _reject) => {
var _sync = true;
function _error(_err) {
if(_sync)
_resolve(Promise.resolve().then(() => { throw _err; }));
else
_reject(_err);
};
var _context;
var _x = this._x;
function _next0() {
var _fn1 = _x[1];
var _hasResult1 = false;
var _promise1 = _fn1();
if (!_promise1 || !_promise1.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
_promise1.then(_result1 => {
_hasResult1 = true;
_resolve();
}, _err1 => {
if(_hasResult1) throw _err1;
_error(_err1);
});
}
var _fn0 = _x[0];
var _hasResult0 = false;
var _promise0 = _fn0();
if (!_promise0 || !_promise0.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
_promise0.then(_result0 => {
_hasResult0 = true;
_next0();
}, _err0 => {
if(_hasResult0) throw _err0;
_error(_err0);
});
_sync = false;
});
})
复制代码
AsyncSeriesHook 调用的是callTapsSeries 生成的执行函数,原理比较简单,就是当一个promise执行完以后再去执行另外一个
// content
content({ onError, onResult, resultReturns, onDone }) {
return this.callTapsSeries({
onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
onResult: (i, result, next) =>
`if(${result} !== undefined) {\n${onResult(
result
)};\n} else {\n${next()}}\n`,
resultReturns,
onDone
});
}
//生成的函数
(function anonymous() {
"use strict";
return new Promise((_resolve, _reject) => {
var _sync = true;
function _error(_err) {
if(_sync)
_resolve(Promise.resolve().then(() => { throw _err; }));
else
_reject(_err);
};
var _context;
var _x = this._x;
function _next0() {
var _fn1 = _x[1];
var _hasResult1 = false;
var _promise1 = _fn1();
if (!_promise1 || !_promise1.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
_promise1.then(_result1 => {
_hasResult1 = true;
if(_result1 !== undefined) {
_resolve(_result1);
;
} else {
_resolve();
}
}, _err1 => {
if(_hasResult1) throw _err1;
_error(_err1);
});
}
var _fn0 = _x[0];
var _hasResult0 = false;
var _promise0 = _fn0();
if (!_promise0 || !_promise0.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
_promise0.then(_result0 => {
_hasResult0 = true;
if(_result0 !== undefined) {
_resolve(_result0);
;
} else {
_next0();
}
}, _err0 => {
if(_hasResult0) throw _err0;
_error(_err0);
});
_sync = false;
});
})
复制代码
这个钩子其实和SyncBailHook的实现是同样的,多一步判断返回值,若是有返回值就直接抛出了
content({ onError, onResult, onDone }) {
return this.callTapsSeries({
onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
onResult: (i, result, next) => {
let code = "";
code += `if(${result} !== undefined) {\n`;
code += `${this._args[0]} = ${result};\n`;
code += `}\n`;
code += next();
return code;
},
onDone: () => onResult(this._args[0])
});
}
//生成的函数
(function anonymous(home
) {
"use strict";
return new Promise((_resolve, _reject) => {
var _sync = true;
function _error(_err) {
if(_sync)
_resolve(Promise.resolve().then(() => { throw _err; }));
else
_reject(_err);
};
var _context;
var _x = this._x;
function _next0() {
var _fn1 = _x[1];
var _hasResult1 = false;
var _promise1 = _fn1(home);
if (!_promise1 || !_promise1.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
_promise1.then(_result1 => {
_hasResult1 = true;
if(_result1 !== undefined) {
home = _result1;
}
_resolve(home);
}, _err1 => {
if(_hasResult1) throw _err1;
_error(_err1);
});
}
var _fn0 = _x[0];
var _hasResult0 = false;
var _promise0 = _fn0(home);
if (!_promise0 || !_promise0.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
_promise0.then(_result0 => {
_hasResult0 = true;
if(_result0 !== undefined) {
home = _result0;
}
_next0();
}, _err0 => {
if(_hasResult0) throw _err0;
_error(_err0);
});
_sync = false;
});
})
复制代码
与SyncWaterfallHook 同样,多一步赋值过程