webpack之tapable
近期研究webpack loader和plugin编写,发现涉及到tapable,闲来无事翻翻源码结合使用方法记录一下
原文 github.com/webpack/tap…
一、钩子类型
tapable提供了很多钩子(Hook classes)
const {
SyncHook, // 同步钩子 从上到下顺序执行
SyncBailHook, // 同步早退钩子 从上到下顺序执行,遇到返回值不是undefined的注册函数时停止执行
SyncWaterfallHook, // 同步瀑布钩子 从上到下执行,依次将返回值传递给下一个函数
SyncLoopHook, // 同步循环钩子 从上到下执行,某个函数可能会执行好几遍,当返回值是undefined会继续执行下个函数
AsyncParallelHook, // 异步并发钩子 异步并行
AsyncParallelBailHook, // 异步并发可早退钩子 异步并行熔断
AsyncSeriesHook, // 异步顺序钩子 异步串行
AsyncSeriesBailHook, // 异步顺序可早退钩子 异步串行熔断
AsyncSeriesWaterfallHook // 异步顺序瀑布钩子 异步串行值传递【瀑布】
} = require("tapable");
二、搭建项目
github.com/17139313271…
三、钩子使用方法
3.1 同步钩子-SyncHook
import { SyncHook } from '../table/lib';
const hook = new SyncHook(); // 创建钩子对象
hook.tap('logPlugin', () => console.log('注册了')); // tap方法注册钩子回调
hook.call(); // call方法调用钩子,打印出‘被勾了’三个字
3.2 同步早退钩子-SyncBailHook
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’
3.3 同步瀑布钩子-SyncWaterfallHook
它的每一步都依赖上一步的执行结果,也就是上一步return的值就是下一步的参数。
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’
3.4 同步循环钩子 -SyncLoopHook
SyncLoopHook是同步的循环钩子,它的插件如果返回一个非undefined。就会一直执行这个插件的回调函数,直到它返回undefined。
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’一次。
3.5异步并发钩子-AsyncParallelHook
当所有的异步任务执行结束后,再最终的回调中执行接下来的代码
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’。紧接着打印‘最终的回调’
3.6异步并发可早退钩子-AsyncParallelBailHook
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’。
3.7 异步顺序钩子- AsyncSeriesHook
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,再立马打印最终的回调。
3.8 异步顺序可早退钩子-AsyncSeriesBailHook
import { AsyncSeriesBailHook } from '../table/lib';
const hook = new AsyncSeriesBailHook();
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,立马打印最终的回调,不会再执行异步事件2了。
3.9 异步顺序瀑布钩子-AsyncSeriesWaterfallHook
import { AsyncSeriesWaterfallHook } from '../table/lib';
const hook = new AsyncSeriesWaterfallHook();
hook.tapPromise('calculateRoutesPlugin1', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('异步事件1', result);
resolve(1);
}, 1000);
});
});
hook.tapPromise('calculateRoutesPlugin2', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('异步事件2', result);
resolve(2);
}, 2000);
});
});
hook.then(() => { console.log('最终的回调'); });
//1s过后,打印异步事件1 undefined,再过2s打印异步事件2 北京,然后立马打印最终的回调。
四、从源码分析钩子实现原理
4.1 流程分析
-
tapable 源码使用工厂模式和模板模式,Hook.js 抽离了功能函数,便于其他钩子函数继承,HookCodeFactory.js则使用模板模式,根据钩子函数的不同生成不同的执行函数返回调用的_call()
-
注册过程
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();
}
4.2 SyncHook的实现
//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()调用的时候挨个执行,比较简单
4.3 SyncBailHook
前面介绍使用方法时说过此钩子根据返回值判断是否继续执行,它的内部构造与 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 多一个判断条件,判断上一个钩子函数的执行结果来决定是否继续执行
4.4 SyncWaterfallHook
//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相比多赋值的操作,判断是否有返回值,如果有返回值就将返回值传给下一个执行函数
4.5 SyncLoopHook
// 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(),
4.6AsyncParallelHook
// 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()
4.7AsyncParallelBailHook
-
// 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();
}
}
4.8AsyncSeriesHook
//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执行完之后再去执行另一个
4.9AsyncSeriesBailHook
// 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的实现是一样的,多一步判断返回值,如果有返回值就直接抛出了
4.10AsyncSeriesWaterfallHook
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 一样,多一步赋值过程
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!