阅读 149

Webpack核心Tapable从应用到源码之一(应用)

前言

Tapable 包是 Webpack 插件机制的核心,它暴露出很多 Hook 供插件使用。

在版本 V2.2.0 中,一共提供了 10Hook

exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
复制代码

此外还有两个工具类

  • 一个是带有 Hooks 映射的帮助类
exports.HookMap = require("./HookMap");
复制代码
  • 一个是可以创建一个 Hook,重定向到其他多个 Hook的帮助类
exports.MultiHook = require("./MultiHook");
复制代码

以上源码都在文件 tapable/lib/index.js 中

Hook 类型

每个 Hook 都可以订阅一个或多个功能,如何运行取决于它们各自的类型。

根据执行流程可以分为以下四种类型:

类型解释
Basic简单的调用它订阅的每一个函数,不关心函数执行的结果
Waterfall在执行订阅函数时,会把当前函数的返回结果,传递到下一个订阅的函数
Bail在执行订阅函数时,如果当前订阅函数的返回结果不是 undefined,则后面订阅的函数不会再继续执行
Loop在执行订阅时,如果当前订阅函数返回的值不是 undefined,它会从第一个订阅的函数开始循环执行

根据同异步又可分为以下三种类型:

类型解释
Sync订阅的函数都是同步函数,不能是异步函数,使用 tap 方法订阅
AsyncSeries异步串行,订阅的函数可以是同步函数或异步函数,使用 taptapAsynctapPromise 方法订阅;顺序执行每个订阅的异步函数
AsyncParallel异步并行,订阅的函数可以是同步函数或异步函数,使用 taptapAsynctapPromise 方法订阅;并行执行每个订阅的异步函数

以上分类反映在其类名中,比如 AsyncSeriesWaterfallHook 就是具有异步串行,并把执行结果传递到下一个订阅函数的功能。

应用示例

下面我们就结合具体的示例,演示一下每一个 Hook 的使用方法。

SyncHook

示例代码:

const {
  SyncHook
} = require('tapable');

// 实例化
const hook = new SyncHook(['name', 'age']);

// 订阅
hook.tap('js', (name, age) => {
  console.log('js', name, age);
});
// 订阅
hook.tap({ name: 'css' }, (name, age) => {
  console.log('css', name, age);
});
// 订阅
hook.tap('node', (name, age) => {
  console.log('node', name, age);
});

// 执行订阅函数
hook.call('naonao', 2);

复制代码

执行结果

js naonao 2
css naonao 2
node naonao 2
复制代码

注意

  1. 构造函数接收一个可选参数,参数类型是一个成员为字符串的数组
  2. 构造函数接收的参数名,可以任意定义,它只是一个形参,但是数组长度一定要和实际接收的参数保持一致
  3. 执行 call 函数传递的参数个数一定要等于实例化时接收的数组长度,且一一对应
  4. call 执行,是根据 tap 订阅的顺序执行即先进先出

改造示例

改造一下上面的示例:

示例代码

const {
  SyncHook
} = require('tapable');

// 实例化,参数['aa', 'bb'] 只关心长度,成员的名字无所谓
const hook = new SyncHook(['aa', 'bb']);

// 订阅 name, age有值,因为 call时只传递了两个参数,所以 sex是undefined
hook.tap('js', (name, age, sex) => {
  console.log('js', name, age, sex);
});
// 订阅
hook.tap({ name: 'css' }, (name, age) => {
  console.log('css', name, age);
});
// 订阅 cc 和 dd 也都是行参,对应的值是和call传递的参数一一对应的
hook.tap('node', (cc, dd) => {
  console.log('node', cc, dd);
});
// 执行订阅 参数个数要和实例化时的长度保持一致
hook.call('naonao', 2);
复制代码

执行结果

js naonao 2 undefined
css naonao 2
node naonao 2
复制代码

打印 hook.call

它为什么会是这样的?我们把 hook.call 打印出来就知道了。

function anonymous(aa, bb) {
  "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);
}
复制代码

这就清晰明了了。

实例化时的数组成员就是 anonymous 函数的形参。

通过 tap 订阅的函数,都分化为 _fn0_fn1_fn2,然后顺序执行它们。

订阅函数执行时,只接收到两个参数,所以第一个订阅函数中的 sexundefined

所有问题都解释通了。

SyncBailHook

示例代码:

const {
  SyncBailHook
} = require('tapable');

const hook = new SyncBailHook(['name', 'age']);

hook.tap('js', (name, age) => {
  console.log('js', name, age);
});
hook.tap({ name: 'css' }, (name, age) => {
  console.log('css', name, age);
  // 返回0 不是undefined,在此结束,后面订阅的函数不会执行
  return 0;
});
hook.tap('node', (name, age) => {
  console.log('node', name, age);
});

hook.call('naonao', 2);
复制代码

执行结果:

js naonao 2
css naonao 2
复制代码

注意

  1. 订阅函数的返回值不是 undefined 时,后面的订阅函数就不会执行了

打印 hook.call

打印此时的 hook.call 方法:

function anonymous(name, age) {
  "use strict";
  var _context;
  var _x = this._x;
  var _fn0 = _x[0];
  var _result0 = _fn0(name, age);
  if (_result0 !== undefined) {
    return _result0;
    ;
  } else {
    var _fn1 = _x[1];
    var _result1 = _fn1(name, age);
    if (_result1 !== undefined) {
      return _result1;
      ;
    } else {
      var _fn2 = _x[2];
      var _result2 = _fn2(name, age);
      if (_result2 !== undefined) {
        return _result2;
        ;
      } else {
      }
    }
  }

}
复制代码

它内部调用订阅函数,并获取订阅函数的执行结果,使用了 !== 运算符对比,所以必须是 undefined时才会继续执行后面的订阅函数。

SyncWaterfallHook

示例代码

const {
  SyncWaterfallHook
} = require('tapable');

const hook = new SyncWaterfallHook(['name', 'age']);

hook.tap('js', (name, age) => {
  console.log('js', name, age);
});
hook.tap({ name: 'css' }, (name, age) => {
  console.log('css', name, age);
  // 返回值不是undefined,将作为下一个订阅函数的第一个参数
  return `name是${name}`;
});
hook.tap('node', (name, age) => {
  console.log('node', name, age);
});

hook.call('naonao', 2);
复制代码

打印结果

js naonao 2
css naonao 2
node name是naonao 2
复制代码

注意

  • 如果订阅函数返回的结果不是 undefined,将作为下一个订阅函数的第一个参数

打印 hook.call

打印此时的 hook.call 方法:

function anonymous(name, age) {
  "use strict";
  var _context;
  var _x = this._x;
  var _fn0 = _x[0];
  var _result0 = _fn0(name, age);
  if (_result0 !== undefined) {
    name = _result0;
  }
  var _fn1 = _x[1];
  var _result1 = _fn1(name, age);
  if (_result1 !== undefined) {
    name = _result1;
  }
  var _fn2 = _x[2];
  var _result2 = _fn2(name, age);
  if (_result2 !== undefined) {
    name = _result2;
  }
  return name;

}
复制代码

它内部会判断如果订阅函数的执行结果不是 undefined 会把结果赋值给 name 即第一个参数,然后继续执行后面的订阅函数。

SyncLoopHook

示例代码

const {
  SyncLoopHook
} = require('tapable');

const hook = new SyncLoopHook(['name', 'age']);
let count1 = 0;
let count2 = 0;
hook.tap('js', (name, age) => {
  console.log('js', name, age);
});
hook.tap({ name: 'css' }, (name, age) => {
  console.log('css', name, age, count1);
  if (++count1 === 2) {
    count1 = 0;
    // 返回值是 undefined,才会执行后面的订阅函数
    return;
  }
  // 订阅函数的返回值,不是 undefined,会从第一个订阅函数重新执行
  return `name是${name}`;
});
hook.tap('node', (name, age) => {
  console.log('node', name, age, count2);
  if (++count2 === 2) {
    count2 = 0;
    return;
  }
  return 0;
});

hook.call('naonao', 2);
复制代码

打印结果

js naonao 2
css naonao 2 0
js naonao 2
css naonao 2 1
node naonao 2 0
js naonao 2
css naonao 2 0
js naonao 2
css naonao 2 1
node naonao 2 1
复制代码

注意

  • 订阅函数的返回值不是 undefined 时,会从第一个订阅函数开始重新执行。

打印 hook.call

打印此时的 hook.call 方法:

function anonymous(name, age) {
  "use strict";
  var _context;
  var _x = this._x;
  var _loop;
  do {
    _loop = false;
    var _fn0 = _x[0];
    var _result0 = _fn0(name, age);
    if (_result0 !== undefined) {
      _loop = true;
    } else {
      var _fn1 = _x[1];
      var _result1 = _fn1(name, age);
      if (_result1 !== undefined) {
        _loop = true;
      } else {
        var _fn2 = _x[2];
        var _result2 = _fn2(name, age);
        if (_result2 !== undefined) {
          _loop = true;
        } else {
          if (!_loop) {
          }
        }
      }
    }
  } while (_loop);

}
复制代码

它内部是一个 do...while 循环,每次执行订阅函数时,如果返回值不是 undefined 就设置 _loop = true 那么循环就会再次执行,所以是从第一个订阅函数开始执行的。

AsyncParallelHook

可以使用 taptapAsynctapPromise 方法进行订阅

tap订阅

示例代码

const {
  AsyncParallelHook
} = require('tapable');

const hook = new AsyncParallelHook(['name', 'age']);

hook.tap('js', (name, age) => {
  // 异步执行
  setTimeout(() => {
    console.log('js', name, age);
  }, 1000);
});
hook.tap({ name: 'css' }, (name, age) => {
  // 异步执行
  setTimeout(() => {
    console.log('css', name, age);
  }, 2000);
});
hook.tap('node', (name, age) => {
  // 同步执行
  console.log('node', name, age);
});

hook.callAsync('naonao', 2, () => {
  console.log('end');
});
复制代码

打印结果

node naonao 2
end
js naonao 2
css naonao 2
复制代码

打印的结果中,一秒后打印 js naonao 2 ,两秒后打印 css naonao 2

注意

  • 要使用 hook.callAsync 方法进行调用。

打印 hook.callAsync

打印此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  do {
    var _counter = 3;
    var _done = (function () {
      _callback();
    });
    if (_counter <= 0) break;
    var _fn0 = _x[0];
    var _hasError0 = false;
    try {
      _fn0(name, age);
    } 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(name, age);
    } 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(name, age);
    } catch (_err) {
      _hasError2 = true;
      if (_counter > 0) {
        _callback(_err);
        _counter = 0;
      }
    }
    if (!_hasError2) {
      if (--_counter === 0) _done();
    }
  } while (false);

}
复制代码

仔细看一下它内部,先用一个计数器记录订阅函数的个数,顺序执行每一个订阅函数,同时捕获订阅函数,把捕获的错误传递给回调函数;每执行一个订阅函数,就让计数器减一,当所有订阅函数都执行后,才执行回调函数。

tapAsync 订阅

示例代码

const {
  AsyncParallelHook
} = require('tapable');

const hook = new AsyncParallelHook(['name', 'age']);

hook.tapAsync('js', (name, age, callback) => {
  // 同步
  console.log('js', name, age);
  callback('js error');
});
hook.tapAsync({ name: 'css' }, (name, age) => {
  // 异步
  setTimeout(() => {
    console.log('css', name, age);
  }, 2000);
});

hook.callAsync('naonao', 2, err => {
  console.log('end', err);
});
复制代码

打印结果

js naonao 2
end js error
复制代码

只输出了 js naonao 2 ,第二个订阅的异步函数并没有执行,而且 callback 函数的参数 js error' 传递给了回调函数。

打印 hook.callAsync

打印一下此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  do {
    var _counter = 2;
    var _done = (function () {
      _callback();
    });
    if (_counter <= 0) break;
    var _fn0 = _x[0];
    _fn0(name, age, (function (_err0) {
      if (_err0) {
        if (_counter > 0) {
          _callback(_err0);
          _counter = 0;
        }
      } else {
        if (--_counter === 0) _done();
      }
    }));
    if (_counter <= 0) break;
    var _fn1 = _x[1];
    _fn1(name, age, (function (_err1) {
      if (_err1) {
        if (_counter > 0) {
          _callback(_err1);
          _counter = 0;
        }
      } else {
        if (--_counter === 0) _done();
      }
    }));
  } while (false);

}
复制代码

它内部是顺序执行订阅函数,但是执行订阅函数时,会给每个订阅函数添加一个回调参数,所以我们再订阅函数中有了 callback。如果我们执行 callback时传递了参数,内部就会立刻把计数器设置为 0 ,停止执行后面的订阅函数。

tapPromise 订阅

示例代码

const {
  AsyncParallelHook
} = require('tapable');

const hook = new AsyncParallelHook(['name', 'age']);

hook.tapPromise('js', (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('js', name, age);
      resolve(1);
    }, 1000);
  });
});
hook.tapPromise({ name: 'css' }, (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('css', name, age);
      resolve(2);
    }, 1000);
  });
});

hook.promise('naonao', 2).then(result => {
  console.log('end', result);
});
复制代码

打印结果

js naonao 2
css naonao 2
end undefined
复制代码

打印结果看,订阅函数中调用 resovle 并没有把值传递下去,因为 resultundefined

注意

  • 使用 tapPromise 订阅,必须返回一个 Promise

打印 hook.promise

打印此时的 hook.promise 方法:

function anonymous(name, age) {
  "use strict";
  var _context;
  var _x = this._x;
  return new Promise((function (_resolve, _reject) {
    var _sync = true;
    function _error(_err) {
      if (_sync)
        _resolve(Promise.resolve().then((function () { throw _err; })));
      else
        _reject(_err);
    };
    do {
      var _counter = 2;
      var _done = (function () {
        _resolve();
      });
      if (_counter <= 0) break;
      var _fn0 = _x[0];
      var _hasResult0 = false;
      var _promise0 = _fn0(name, age);
      if (!_promise0 || !_promise0.then)
        throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
      _promise0.then((function (_result0) {
        _hasResult0 = true;
        if (--_counter === 0) _done();
      }), function (_err0) {
        if (_hasResult0) throw _err0;
        if (_counter > 0) {
          _error(_err0);
          _counter = 0;
        }
      });
      if (_counter <= 0) break;
      var _fn1 = _x[1];
      var _hasResult1 = false;
      var _promise1 = _fn1(name, age);
      if (!_promise1 || !_promise1.then)
        throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
      _promise1.then((function (_result1) {
        _hasResult1 = true;
        if (--_counter === 0) _done();
      }), function (_err1) {
        if (_hasResult1) throw _err1;
        if (_counter > 0) {
          _error(_err1);
          _counter = 0;
        }
      });
    } while (false);
    _sync = false;
  }));

}
复制代码

分析一下,它内部返回了一个 Promise

在调用订阅函数后,它内判断返回的结果是否为 Promise,否则将抛出错误。

订阅函数执行 resolve 的结果,并没有被使用,所以在最后的结果中 resultundefined

AsyncParallelBailHook

这是一个异步并行,且订阅函数返回值不是 undefined 时会中断的 Hook

可以使用 taptapAsynctapPromise 方法进行订阅

tap 订阅

示例代码

const {
  AsyncParallelBailHook
} = require('tapable');

const hook = new AsyncParallelBailHook(['name', 'age']);

hook.tap('js', (name, age) => {
  // 异步执行
  setTimeout(() => {
    console.log('js', name, age);
  }, 1000);
});
hook.tap({ name: 'css' }, (name, age) => {
  // 异步执行
  setTimeout(() => {
    console.log('css', name, age);
  }, 2000);
  return 'wrong';
});
hook.tap('node', (name, age) => {
  // 同步执行
  console.log('node', name, age);
});

hook.callAsync('naonao', 2, err => {
  console.log('end', err);
});
复制代码

打印结果

end null
js naonao 2
css naonao 2
复制代码

从执行结果看,最后一个订阅函数 node 并没有打印出来。

所以如果中间某个订阅函数的返回值不是 undefined ,后面的订阅函数就不会继续执行了。

打印 hook.callAsync

打印此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  var _results = new Array(3);
  var _checkDone = function () {
    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 = (function () {
      _callback();
    });
    if (_counter <= 0) break;
    var _fn0 = _x[0];
    var _hasError0 = false;
    try {
      var _result0 = _fn0(name, age);
    } 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();
        }
      }
    }
    if (_counter <= 0) break;
    if (1 >= _results.length) {
      if (--_counter === 0) _done();
    } else {
      var _fn1 = _x[1];
      var _hasError1 = false;
      try {
        var _result1 = _fn1(name, age);
      } catch (_err) {
        _hasError1 = true;
        if (_counter > 0) {
          if (1 < _results.length && ((_results.length = 2), (_results[1] = { error: _err }), _checkDone())) {
            _counter = 0;
          } else {
            if (--_counter === 0) _done();
          }
        }
      }
      if (!_hasError1) {
        if (_counter > 0) {
          if (1 < _results.length && (_result1 !== undefined && (_results.length = 2), (_results[1] = { result: _result1 }), _checkDone())) {
            _counter = 0;
          } else {
            if (--_counter === 0) _done();
          }
        }
      }
    }
    if (_counter <= 0) break;
    if (2 >= _results.length) {
      if (--_counter === 0) _done();
    } else {
      var _fn2 = _x[2];
      var _hasError2 = false;
      try {
        var _result2 = _fn2(name, age);
      } catch (_err) {
        _hasError2 = true;
        if (_counter > 0) {
          if (2 < _results.length && ((_results.length = 3), (_results[2] = { error: _err }), _checkDone())) {
            _counter = 0;
          } else {
            if (--_counter === 0) _done();
          }
        }
      }
      if (!_hasError2) {
        if (_counter > 0) {
          if (2 < _results.length && (_result2 !== undefined && (_results.length = 3), (_results[2] = { result: _result2 }), _checkDone())) {
            _counter = 0;
          } else {
            if (--_counter === 0) _done();
          }
        }
      }
    }
  } while (false);

}
复制代码

重点看一下 _checkDone 方法,它在遍历时,如果 item.result !== undefined 就会直接调用 _callback

再向下看 do...while 循环,每个订阅函数执行时如果没有错误产生,就会把它的结果赋值给 _result 对应的数组成员,然后再调用 _checkDone 方法。

if (0 < _results.length && (_result0 !== undefined && (_results.length = 1), (_results[0] = { result: _result0 }), _checkDone())) {
    _counter = 0;
}
复制代码

tapAsync 订阅

示例代码

const {
  AsyncParallelBailHook
} = require('tapable');

const hook = new AsyncParallelBailHook(['name', 'age']);

hook.tapAsync('js', (name, age, callback) => {
  // 异步执行
  setTimeout(() => {
    console.log('js', name, age);
  }, 1000);
  callback();
});
hook.tapAsync({ name: 'css' }, (name, age, callback) => {
  // 异步执行
  setTimeout(() => {
    console.log('css', name, age);
  }, 2000);
  callback('css error');
});
hook.tapAsync('node', (name, age, callback) => {
  // 同步执行
  console.log('node', name, age);
  callback();
});

hook.callAsync('naonao', 2, err => {
  console.log('end', err);
});
复制代码

打印结果

end css error
js naonao 2
css naonao 2
复制代码

从打印结果看,最后一个订阅函数 node 并没有打印出来。

注意

  • 如果 订阅函数 callback 有参数,即终止后面的订阅函数执行
  • 每个订阅函数都会多一个回调函数的参数,且必须执行

打印 hook.callAsync

打印此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  var _results = new Array(3);
  var _checkDone = function () {
    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 = (function () {
      _callback();
    });
    if (_counter <= 0) break;
    var _fn0 = _x[0];
    _fn0(name, age, (function (_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(name, age, (function (_err1, _result1) {
        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();
            }
          }
        }
      }));
    }
    if (_counter <= 0) break;
    if (2 >= _results.length) {
      if (--_counter === 0) _done();
    } else {
      var _fn2 = _x[2];
      _fn2(name, age, (function (_err2, _result2) {
        if (_err2) {
          if (_counter > 0) {
            if (2 < _results.length && ((_results.length = 3), (_results[2] = { error: _err2 }), _checkDone())) {
              _counter = 0;
            } else {
              if (--_counter === 0) _done();
            }
          }
        } else {
          if (_counter > 0) {
            if (2 < _results.length && (_result2 !== undefined && (_results.length = 3), (_results[2] = { result: _result2 }), _checkDone())) {
              _counter = 0;
            } else {
              if (--_counter === 0) _done();
            }
          }
        }
      }));
    }
  } while (false);

}
复制代码

因为每个订阅函数都会添加一个 callback 参数,而执行结果是在这个 callback 中做的处理,所以每个订阅函数都必须执行 callback ,否则 _checkDonecallAsynccallback 就可能不会被执行。

tapPromise 订阅

示例代码

const {
  AsyncParallelBailHook
} = require('tapable');

const hook = new AsyncParallelBailHook(['name', 'age']);

hook.tapPromise('js', (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('js', name, age);
      resolve(1);
    }, 2000);
  });
});
hook.tapPromise({ name: 'css' }, (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('css', name, age);
      resolve(2);
    }, 1000);
  });
});
hook.tapPromise('node', (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('node', name, age);
      resolve(3);
    }, 3000);
  });
});

hook.promise('naonao', 2).then(result => {
  console.log('end', result);
}, err => {
  console.log('end', erro);
});
复制代码

打印结果

css naonao 2
js naonao 2
end 1
node naonao 2
复制代码

最后 end 打印的 1, 并不是 3。而且 end 后继续打印 node nanonao 2

注意

  • 只要有一个订阅函数 resolvereject 就会立刻调用 hook.promisethen 方法
  • hook.promisethenresolve 值是按订阅函数顺序取拥有第一个的 resolve 值。

打印 hook.promise

打印此时的 hook.promise 方法:

function anonymous(name, age) {
  "use strict";
  var _context;
  var _x = this._x;
  return new Promise((function (_resolve, _reject) {
    var _sync = true;
    function _error(_err) {
      if (_sync)
        _resolve(Promise.resolve().then((function () { throw _err; })));
      else
        _reject(_err);
    };
    var _results = new Array(3);
    var _checkDone = function () {
      for (var i = 0; i < _results.length; i++) {
        var item = _results[i];
        if (item === undefined) return false;
        if (item.result !== undefined) {
          _resolve(item.result);
          return true;
        }
        if (item.error) {
          _error(item.error);
          return true;
        }
      }
      return false;
    }
    do {
      var _counter = 3;
      var _done = (function () {
        _resolve();
      });
      if (_counter <= 0) break;
      var _fn0 = _x[0];
      var _hasResult0 = false;
      var _promise0 = _fn0(name, age);
      if (!_promise0 || !_promise0.then)
        throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
      _promise0.then((function (_result0) {
        _hasResult0 = true;
        if (_counter > 0) {
          if (0 < _results.length && (_result0 !== undefined && (_results.length = 1), (_results[0] = { result: _result0 }), _checkDone())) {
            _counter = 0;
          } else {
            if (--_counter === 0) _done();
          }
        }
      }), function (_err0) {
        if (_hasResult0) throw _err0;
        if (_counter > 0) {
          if (0 < _results.length && ((_results.length = 1), (_results[0] = { error: _err0 }), _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];
        var _hasResult1 = false;
        var _promise1 = _fn1(name, age);
        if (!_promise1 || !_promise1.then)
          throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
        _promise1.then((function (_result1) {
          _hasResult1 = true;
          if (_counter > 0) {
            if (1 < _results.length && (_result1 !== undefined && (_results.length = 2), (_results[1] = { result: _result1 }), _checkDone())) {
              _counter = 0;
            } else {
              if (--_counter === 0) _done();
            }
          }
        }), function (_err1) {
          if (_hasResult1) throw _err1;
          if (_counter > 0) {
            if (1 < _results.length && ((_results.length = 2), (_results[1] = { error: _err1 }), _checkDone())) {
              _counter = 0;
            } else {
              if (--_counter === 0) _done();
            }
          }
        });
      }
      if (_counter <= 0) break;
      if (2 >= _results.length) {
        if (--_counter === 0) _done();
      } else {
        var _fn2 = _x[2];
        var _hasResult2 = false;
        var _promise2 = _fn2(name, age);
        if (!_promise2 || !_promise2.then)
          throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise2 + ')');
        _promise2.then((function (_result2) {
          _hasResult2 = true;
          if (_counter > 0) {
            if (2 < _results.length && (_result2 !== undefined && (_results.length = 3), (_results[2] = { result: _result2 }), _checkDone())) {
              _counter = 0;
            } else {
              if (--_counter === 0) _done();
            }
          }
        }), function (_err2) {
          if (_hasResult2) throw _err2;
          if (_counter > 0) {
            if (2 < _results.length && ((_results.length = 3), (_results[2] = { error: _err2 }), _checkDone())) {
              _counter = 0;
            } else {
              if (--_counter === 0) _done();
            }
          }
        });
      }
    } while (false);
    _sync = false;
  }));

}
复制代码

重点看 _checkDone 方法,如果订阅函数的 resolve 不是 undefined 就会离开调用 _resolve

再看 do...while ,每个订阅函数的 resolve 会存储到对应索引的 _result 值里,如果值不是 undefined 就会执行 _checkDone 方法。

AsyncSeriesHook

异步串行 Hook

可以使用 taptapAsynctapPromise 方法进行订阅

tap 订阅

示例代码

const {
  AsyncSeriesHook
} = require('tapable');

const hook = new AsyncSeriesHook(['name', 'age']);

hook.tap('js', (name, age) => {
  setTimeout(() => {
    console.log('js', name, age);
  }, 2000);
});
hook.tap({ name: 'css' }, (name, age) => {
  setTimeout(() => {
    console.log('css', name, age);
  }, 1000);
});
hook.tap('node', (name, age) => {
  setTimeout(() => {
    console.log('node', name, age);
  }, 3000);
});

hook.callAsync('naonao', 2, result => {
  console.log('end', result);
});
复制代码

打印结果:

end undefined
css naonao 2
js naonao 2
node naonao 2
复制代码

从打印结果看,串行顺序执行订阅函数。

注意

  • 订阅函数是串行顺序执行,如果遇到错误就会中断执行后面的订阅函数

打印 hook.callAsync

打印此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  var _fn0 = _x[0];
  var _hasError0 = false;
  try {
    _fn0(name, age);
  } catch (_err) {
    _hasError0 = true;
    _callback(_err);
  }
  if (!_hasError0) {
    var _fn1 = _x[1];
    var _hasError1 = false;
    try {
      _fn1(name, age);
    } catch (_err) {
      _hasError1 = true;
      _callback(_err);
    }
    if (!_hasError1) {
      var _fn2 = _x[2];
      var _hasError2 = false;
      try {
        _fn2(name, age);
      } catch (_err) {
        _hasError2 = true;
        _callback(_err);
      }
      if (!_hasError2) {
        _callback();
      }
    }
  }

}
复制代码

可以看出,它是按照订阅函数的顺序执行的,如果执行过程遇到错误就会立刻调用回调函数。

tapAsync 订阅

示例代码

const {
  AsyncSeriesHook
} = require('tapable');

const hook = new AsyncSeriesHook(['name', 'age']);

hook.tapAsync('js', (name, age, callback) => {
  setTimeout(() => {
    console.log('js', name, age);
  }, 2000);
  callback();
});
hook.tapAsync({ name: 'css' }, (name, age, callback) => {
  setTimeout(() => {
    console.log('css', name, age);
  }, 1000);
  callback('css error');
});
hook.tapAsync('node', (name, age, callback) => {
  setTimeout(() => {
    console.log('node', name, age);
  }, 3000);
  callback();
});

hook.callAsync('naonao', 2, result => {
  console.log('end', result);
});
复制代码

打印结果

end css error
css naonao 2
js naonao 2
复制代码

从打印结果看,第三个订阅函数 node 并没有被打印出来。但是 js 后于 css 打印。

注意

  • 如果订阅函数的 callback 传递了非 undefined 值,后面的订阅函数不会被执行
  • 订阅函数是按照顺序执行,但是如果有异步代码,仍然按照事件环的方式执行

打印 hook.callAsync

打印此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  function _next1() {
    var _fn2 = _x[2];
    _fn2(name, age, (function (_err2) {
      if (_err2) {
        _callback(_err2);
      } else {
        _callback();
      }
    }));
  }
  function _next0() {
    var _fn1 = _x[1];
    _fn1(name, age, (function (_err1) {
      if (_err1) {
        _callback(_err1);
      } else {
        _next1();
      }
    }));
  }
  var _fn0 = _x[0];
  _fn0(name, age, (function (_err0) {
    if (_err0) {
      _callback(_err0);
    } else {
      _next0();
    }
  }));

}
复制代码

重点看一下每一个订阅函数执行时会添加一个回调的参数,回调参数里会调用下一个订阅函数;如果订阅函数执行错误,就会离开执行最终的回调函数,而不会执行下一个订阅函数,这样达到顺序串行的效果。

tapPromise 订阅

示例代码

const {
  AsyncSeriesHook
} = require('tapable');

const hook = new AsyncSeriesHook(['name', 'age']);

hook.tapPromise('js', (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('js', name, age);
      resolve();
    }, 2000);
  });
});
hook.tapPromise({ name: 'css' }, (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('css', name, age);
      resolve(2);
    }, 1000);
  });
});
hook.tapPromise('node', (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('node', name, age);
      resolve(3);
    }, 3000);
  });
});

hook.promise('naonao', 2).then(result => {
  console.log('end', result);
}, err => {
  console.log('end', erro);
});
复制代码

打印结果

js naonao 2
css naonao 2
node naonao 2
end undefined
复制代码

从打印结果看,它是顺序执行的,而且订阅函数的 resolve 值并没有传递给最终的回调函数。

注意

  • 执行是按照订阅的顺序执行

打印 hook.promise

打印此时的 hook.promise 方法:

function anonymous(name, age) {
  "use strict";
  var _context;
  var _x = this._x;
  return new Promise((function (_resolve, _reject) {
    var _sync = true;
    function _error(_err) {
      if (_sync)
        _resolve(Promise.resolve().then((function () { throw _err; })));
      else
        _reject(_err);
    };
    function _next1() {
      var _fn2 = _x[2];
      var _hasResult2 = false;
      var _promise2 = _fn2(name, age);
      if (!_promise2 || !_promise2.then)
        throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise2 + ')');
      _promise2.then((function (_result2) {
        _hasResult2 = true;
        _resolve();
      }), function (_err2) {
        if (_hasResult2) throw _err2;
        _error(_err2);
      });
    }
    function _next0() {
      var _fn1 = _x[1];
      var _hasResult1 = false;
      var _promise1 = _fn1(name, age);
      if (!_promise1 || !_promise1.then)
        throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
      _promise1.then((function (_result1) {
        _hasResult1 = true;
        _next1();
      }), function (_err1) {
        if (_hasResult1) throw _err1;
        _error(_err1);
      });
    }
    var _fn0 = _x[0];
    var _hasResult0 = false;
    var _promise0 = _fn0(name, age);
    if (!_promise0 || !_promise0.then)
      throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
    _promise0.then((function (_result0) {
      _hasResult0 = true;
      _next0();
    }), function (_err0) {
      if (_hasResult0) throw _err0;
      _error(_err0);
    });
    _sync = false;
  }));

}
复制代码

重点看一下每一个订阅函数执行的 then 方法, _promise0.then 方法内部调用 _next0,而它内部是调用第二个订阅函数 _promise1_promise1.then 内部接着调用 _next1,这样达到穿行的执行效果。

AsyncSeriesBailHook

异步串行 Hook,但是只要订阅函数的返回值不是 undefined 就会中断执行后面的订阅函数。

可以使用 taptapAsynctapPromise 方法进行订阅

tap 订阅

示例代码

const {
  AsyncSeriesBailHook
} = require('tapable');

const hook = new AsyncSeriesBailHook(['name', 'age']);

hook.tap('js', (name, age) => {
  setTimeout(() => {
    console.log('js', name, age);
  }, 2000);
});
hook.tap({ name: 'css' }, (name, age) => {
  setTimeout(() => {
    console.log('css', name, age);
  }, 1000);
  return 'wrong';
});
hook.tap('node', (name, age) => {
  setTimeout(() => {
    console.log('node', name, age);
  }, 3000);
});

hook.callAsync('naonao', 2, result => {
  console.log('end', result);
});
复制代码

打印结果

end null
css naonao 2
js naonao 2
复制代码

从打印结果看,最后一个订阅函数 node 并没有被打印,因为第二个订阅函数返回了一个非 undefined 的值。

打印 hook.callAsync

打印此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  var _fn0 = _x[0];
  var _hasError0 = false;
  try {
    var _result0 = _fn0(name, age);
  } 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(name, age);
      } 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(name, age);
          } catch (_err) {
            _hasError2 = true;
            _callback(_err);
          }
          if (!_hasError2) {
            if (_result2 !== undefined) {
              _callback(null, _result2);

            } else {
              _callback();
            }
          }
        }
      }
    }
  }

}
复制代码

从代码可以看出,它是按顺序执行,如果订阅函数的返回值不是 undefined 或者 有错误,都会立刻执行最终的回调。

tapAsync 订阅

示例代码

const {
  AsyncSeriesBailHook
} = require('tapable');

const hook = new AsyncSeriesBailHook(['name', 'age']);

hook.tapAsync('js', (name, age, callback) => {
  setTimeout(() => {
    console.log('js', name, age);
  }, 2000);
  callback();
});
hook.tapAsync({ name: 'css' }, (name, age, callback) => {
  setTimeout(() => {
    console.log('css', name, age);
  }, 1000);
  callback('css error');
});
hook.tapAsync('node', (name, age, callback) => {
  setTimeout(() => {
    console.log('node', name, age);
  }, 3000);
  callback();
});

hook.callAsync('naonao', 2, result => {
  console.log('end', result);
});
复制代码

打印结果

end css error
css naonao 2
js naonao 2
复制代码

从打印结果看,最后一个订阅函数 node 并没有被打印。

之所以先打印出 css,在打印 js,那是因为内部 setTimeout 引起的。

打印 hook.callAsync

打印此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  function _next1() {
    var _fn2 = _x[2];
    _fn2(name, age, (function (_err2, _result2) {
      if (_err2) {
        _callback(_err2);
      } else {
        if (_result2 !== undefined) {
          _callback(null, _result2);

        } else {
          _callback();
        }
      }
    }));
  }
  function _next0() {
    var _fn1 = _x[1];
    _fn1(name, age, (function (_err1, _result1) {
      if (_err1) {
        _callback(_err1);
      } else {
        if (_result1 !== undefined) {
          _callback(null, _result1);

        } else {
          _next1();
        }
      }
    }));
  }
  var _fn0 = _x[0];
  _fn0(name, age, (function (_err0, _result0) {
    if (_err0) {
      _callback(_err0);
    } else {
      if (_result0 !== undefined) {
        _callback(null, _result0);

      } else {
        _next0();
      }
    }
  }));

}
复制代码

从代码中可以看出,每个订阅函数都会添加一个 callback 的参数,它内部执行,如果有错误或返回值不是 undefined 都会立刻执行最终的回调,否则才会执行下一个订阅函数。

这样就达到了顺序执行的效果。

tapPromise 订阅

示例代码

const {
  AsyncSeriesBailHook
} = require('tapable');

const hook = new AsyncSeriesBailHook(['name', 'age']);

hook.tapPromise('js', (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('js', name, age);
      resolve();
    }, 2000);
  });
});
hook.tapPromise({ name: 'css' }, (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('css', name, age);
      resolve(2);
    }, 1000);
  });
});
hook.tapPromise('node', (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('node', name, age);
      resolve(3);
    }, 3000);
  });
});

hook.promise('naonao', 2).then(result => {
  console.log('end', result);
}, err => {
  console.log('end', err);
});
复制代码

打印结果

js naonao 2
css naonao 2
end 2
复制代码

从打印结果看,最后一个订阅函数 node 并没有被打印。

执行是按照顺序串行执行的,当遇到订阅函数的 resolve 传递的不是 undefined 值时,就会执行最终的 promise.then,后面的订阅函数就不会再执行了。

打印 hook.promise

打印此时的 hook.promise 方法:

function anonymous(name, age) {
  "use strict";
  var _context;
  var _x = this._x;
  return new Promise((function (_resolve, _reject) {
    var _sync = true;
    function _error(_err) {
      if (_sync)
        _resolve(Promise.resolve().then((function () { throw _err; })));
      else
        _reject(_err);
    };
    function _next1() {
      var _fn2 = _x[2];
      var _hasResult2 = false;
      var _promise2 = _fn2(name, age);
      if (!_promise2 || !_promise2.then)
        throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise2 + ')');
      _promise2.then((function (_result2) {
        _hasResult2 = true;
        if (_result2 !== undefined) {
          _resolve(_result2);

        } else {
          _resolve();
        }
      }), function (_err2) {
        if (_hasResult2) throw _err2;
        _error(_err2);
      });
    }
    function _next0() {
      var _fn1 = _x[1];
      var _hasResult1 = false;
      var _promise1 = _fn1(name, age);
      if (!_promise1 || !_promise1.then)
        throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
      _promise1.then((function (_result1) {
        _hasResult1 = true;
        if (_result1 !== undefined) {
          _resolve(_result1);

        } else {
          _next1();
        }
      }), function (_err1) {
        if (_hasResult1) throw _err1;
        _error(_err1);
      });
    }
    var _fn0 = _x[0];
    var _hasResult0 = false;
    var _promise0 = _fn0(name, age);
    if (!_promise0 || !_promise0.then)
      throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
    _promise0.then((function (_result0) {
      _hasResult0 = true;
      if (_result0 !== undefined) {
        _resolve(_result0);

      } else {
        _next0();
      }
    }), function (_err0) {
      if (_hasResult0) throw _err0;
      _error(_err0);
    });
    _sync = false;
  }));

}
复制代码

从代码中能看出,订阅函数的 then 方法,如果 resolve 的值不是 undefined 就会调用最终的 _resolve 方法,并把值传递下去,否则调用下一个订阅函数。

AsyncSeriesLoopHook

异步串行循环 Hook 可以使用 taptapAsynctapPromise 方法进行订阅

tap 订阅

示例代码

const {
  AsyncSeriesLoopHook
} = require('tapable');

const hook = new AsyncSeriesLoopHook(['name', 'age']);
let count1 = 0;
let count2 = 0;
hook.tap('js', (name, age) => {
  if (++count1 >= 2) {
    console.log('js', name, age, count1);
    return;
  } else {
    console.log('js', name, age, count1);
    return 'js1';
  }
});
hook.tap('node', (name, age) => {
  if (++count2 >= 2) {
    console.log('node', name, age, count2);
    return;
  } else {
    console.log('node', name, age, count2);
    return 'node2';
  }
});

hook.callAsync('naonao', 2, err => {
  console.log('end', err);
});
复制代码

打印结果

js naonao 2 1
js naonao 2 2
node naonao 2 1
js naonao 2 3
node naonao 2 2
end undefined
复制代码

从打印结果看,订阅函数返回值不是 undefined 时,会从第一个订阅开始循环执行。

打印 hook.callAsync

打印此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  var _loop;
  do {
    _loop = false;
    var _fn0 = _x[0];
    var _hasError0 = false;
    try {
      var _result0 = _fn0(name, age);
    } catch (_err) {
      _hasError0 = true;
      _callback(_err);
    }
    if (!_hasError0) {
      if (_result0 !== undefined) {
        _loop = true;
      } else {
        var _fn1 = _x[1];
        var _hasError1 = false;
        try {
          var _result1 = _fn1(name, age);
        } catch (_err) {
          _hasError1 = true;
          _callback(_err);
        }
        if (!_hasError1) {
          if (_result1 !== undefined) {
            _loop = true;
          } else {
            if (!_loop) {
              _callback();
            }
          }
        }
      }
    }
  } while (_loop);

}
复制代码

从代码中能看出,每个订阅函数的返回值如果不是 undefined 就会设置 _looptrue ,则 do...while 就会从头开始循环。

tapAsync 订阅

示例代码

const {
  AsyncSeriesLoopHook
} = require('tapable');

const hook = new AsyncSeriesLoopHook(['name', 'age']);
let count1 = 0;
let count2 = 0;

hook.tapAsync('js', (name, age, callback) => {
  if (++count1 >= 2) {
    console.log('js', name, age, count1);
    callback();
  } else {
    console.log('js', name, age, count1);
    callback(null, 'js1');
  }
});

hook.tapAsync('node', (name, age, callback) => {
  if (++count2 >= 2) {
    console.log('node', name, age, count2);
    callback();
  } else {
    console.log('node', name, age, count2);
    callback(null, 'node2');
  }
});

hook.callAsync('naonao', 2, err => {
  console.log('end', err);
});
复制代码

打印结果

js naonao 2 1
js naonao 2 2
node naonao 2 1
js naonao 2 3
node naonao 2 2
end undefined
复制代码

从打印结果看,如果 callback 第二个参数不是 undefined 就会从头开始循环。

打印 hook.callAsync

打印此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  var _looper = (function () {
    var _loopAsync = false;
    var _loop;
    do {
      _loop = false;
      function _next0() {
        var _fn1 = _x[1];
        _fn1(name, age, (function (_err1, _result1) {
          if (_err1) {
            _callback(_err1);
          } else {
            if (_result1 !== undefined) {
              _loop = true;
              if (_loopAsync) _looper();
            } else {
              if (!_loop) {
                _callback();
              }
            }
          }
        }));
      }
      var _fn0 = _x[0];
      _fn0(name, age, (function (_err0, _result0) {
        if (_err0) {
          _callback(_err0);
        } else {
          if (_result0 !== undefined) {
            _loop = true;
            if (_loopAsync) _looper();
          } else {
            _next0();
          }
        }
      }));
    } while (_loop);
    _loopAsync = true;
  });
  _looper();

}
复制代码

从代码中能看出,定义了一个 _looper 方法,每个订阅函数都会有一个回调函数作为参数,如果这个回调函数第二个参数不是 undefined,就会再次调用 _looper 方法,这样就达到了从定一个订阅函数循环执行的效果。

tapPromise 订阅

示例代码

const {
  AsyncSeriesLoopHook
} = require('tapable');

const hook = new AsyncSeriesLoopHook(['name', 'age']);
let count1 = 0;
let count2 = 0;
hook.tapPromise('js', (name, age) => {
  return new Promise((resolve, reject) => {
    if (++count1 >= 2) {
      console.log('js', name, age, count1);
      resolve();
    } else {
      console.log('js', name, age, count1);
      resolve('js1');
    }
  });
});

hook.tapPromise('node', (name, age) => {
  return new Promise((resolve, reject) => {
    if (++count2 >= 2) {
      console.log('node', name, age, count1);
      resolve();
    } else {
      console.log('node', name, age, count1);
      resolve('node2');
    }
  });
});

hook.promise('naonao', 2).then(result => {
  console.log('end', result);
}, err => {
  console.log('end', err);
});
复制代码

打印结果

js naonao 2 1
js naonao 2 2
node naonao 2 2
js naonao 2 3
node naonao 2 3
end undefined
复制代码

从打印结果看,订阅函数的 resolve 不是 undefined 就会从头循环执行,直到遇到 undefined 才会执行下一个订阅函数。

打印 hook.promise

打印此时的 hook.promise 方法:

function anonymous(name, age) {
  "use strict";
  var _context;
  var _x = this._x;
  return new Promise((function (_resolve, _reject) {
    var _sync = true;
    function _error(_err) {
      if (_sync)
        _resolve(Promise.resolve().then((function () { throw _err; })));
      else
        _reject(_err);
    };
    var _looper = (function () {
      var _loopAsync = false;
      var _loop;
      do {
        _loop = false;
        function _next0() {
          var _fn1 = _x[1];
          var _hasResult1 = false;
          var _promise1 = _fn1(name, age);
          if (!_promise1 || !_promise1.then)
            throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
          _promise1.then((function (_result1) {
            _hasResult1 = true;
            if (_result1 !== undefined) {
              _loop = true;
              if (_loopAsync) _looper();
            } else {
              if (!_loop) {
                _resolve();
              }
            }
          }), function (_err1) {
            if (_hasResult1) throw _err1;
            _error(_err1);
          });
        }
        var _fn0 = _x[0];
        var _hasResult0 = false;
        var _promise0 = _fn0(name, age);
        if (!_promise0 || !_promise0.then)
          throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
        _promise0.then((function (_result0) {
          _hasResult0 = true;
          if (_result0 !== undefined) {
            _loop = true;
            if (_loopAsync) _looper();
          } else {
            _next0();
          }
        }), function (_err0) {
          if (_hasResult0) throw _err0;
          _error(_err0);
        });
      } while (_loop);
      _loopAsync = true;
    });
    _looper();
    _sync = false;
  }));

}
复制代码

从代码中可以看出,它定义了一个 _looper 函数,当订阅函数的 resolve 不是 undefined 就会重新执行这个 _looper 函数,也就从第一个订阅函数开始执行了。

AsyncSeriesWaterfallHook

异步串行 Hook,但是会把当前订阅函数的返回值(非undefined)传递给下一个订阅函数,并当作定一个参数使用。

可以使用 taptapAsynctapPromise 方法进行订阅

tap 订阅

示例代码

const {
  AsyncSeriesWaterfallHook
} = require('tapable');

const hook = new AsyncSeriesWaterfallHook(['name', 'age']);

hook.tap('js', (name, age) => {
  setTimeout(() => {
    console.log('js', name, age);
  }, 2000);
  return 'js1';
});
hook.tap({ name: 'css' }, (name, age) => {
  setTimeout(() => {
    console.log('css', name, age);
  }, 1000);
  return 'css2';
});
hook.tap('node', (name, age) => {
  setTimeout(() => {
    console.log('node', name, age);
  }, 3000);
  return 'node3';
});

hook.callAsync('naonao', 2, (err, result) => {
  console.log('end', err, result);
});
复制代码

打印结果

end null node3
css js1 2
js naonao 2
node css2 2
复制代码

从打印结果看出,上一个订阅函数的返回值(非 undefined)传递到了当前订阅函数中,并且作为第一个参数使用。

打印 hook.callAsync

打印此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  var _fn0 = _x[0];
  var _hasError0 = false;
  try {
    var _result0 = _fn0(name, age);
  } catch (_err) {
    _hasError0 = true;
    _callback(_err);
  }
  if (!_hasError0) {
    if (_result0 !== undefined) {
      name = _result0;
    }
    var _fn1 = _x[1];
    var _hasError1 = false;
    try {
      var _result1 = _fn1(name, age);
    } catch (_err) {
      _hasError1 = true;
      _callback(_err);
    }
    if (!_hasError1) {
      if (_result1 !== undefined) {
        name = _result1;
      }
      var _fn2 = _x[2];
      var _hasError2 = false;
      try {
        var _result2 = _fn2(name, age);
      } catch (_err) {
        _hasError2 = true;
        _callback(_err);
      }
      if (!_hasError2) {
        if (_result2 !== undefined) {
          name = _result2;
        }
        _callback(null, name);
      }
    }
  }

}
复制代码

从代码中可以看出,每个订阅函数的返回值,如果不是 undefined 就会赋值给第一个参数,然后再继续调用下一个订阅函数。

在看一下 _callback 方法的调用,它的第二个参数就是 callAsync 调用时传递的第一个参数。

tapAsync 订阅

示例代码

const {
  AsyncSeriesWaterfallHook
} = require('tapable');

const hook = new AsyncSeriesWaterfallHook(['name', 'age']);

hook.tapAsync('js', (name, age, callback) => {
  setTimeout(() => {
    console.log('js', name, age);
  }, 2000);
  callback(null, 'js1');
});
hook.tapAsync({ name: 'css' }, (name, age, callback) => {
  setTimeout(() => {
    console.log('css', name, age);
  }, 1000);
  callback(null, 'css2');
});
hook.tapAsync('node', (name, age, callback) => {
  setTimeout(() => {
    console.log('node', name, age);
  }, 3000);
  callback(null, 'node3');
});

hook.callAsync('naonao', 2, (err, result) => {
  console.log('end', err, result);
});
复制代码

打印结果

end null node3
css js1 2
js naonao 2
node css2 2
复制代码

从打印结果看,订阅函数中 callback 函数的第二个参数会传递下去。

打印 hook.callAsync

打印此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  function _next1() {
    var _fn2 = _x[2];
    _fn2(name, age, (function (_err2, _result2) {
      if (_err2) {
        _callback(_err2);
      } else {
        if (_result2 !== undefined) {
          name = _result2;
        }
        _callback(null, name);
      }
    }));
  }
  function _next0() {
    var _fn1 = _x[1];
    _fn1(name, age, (function (_err1, _result1) {
      if (_err1) {
        _callback(_err1);
      } else {
        if (_result1 !== undefined) {
          name = _result1;
        }
        _next1();
      }
    }));
  }
  var _fn0 = _x[0];
  _fn0(name, age, (function (_err0, _result0) {
    if (_err0) {
      _callback(_err0);
    } else {
      if (_result0 !== undefined) {
        name = _result0;
      }
      _next0();
    }
  }));

}
复制代码

从代码中可以看出,订阅函数第三个参数是一个方法,会把方法的第二个参数值(非 undefined) 赋值给 name。然后再调用下一个订阅函数。

tapPromise 订阅

示例代码

const {
  AsyncSeriesWaterfallHook
} = require('tapable');

const hook = new AsyncSeriesWaterfallHook(['name', 'age']);

hook.tapPromise('js', (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('js', name, age);
      resolve('js1');
    }, 2000);
  });
});
hook.tapPromise({ name: 'css' }, (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('css', name, age);
      resolve('css2');
    }, 1000);
  });
});
hook.tapPromise('node', (name, age) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('node', name, age);
      resolve('node3');
    }, 3000);
  });
});

hook.promise('naonao', 2).then(result => {
  console.log('end', result);
}, err => {
  console.log('end', err);
});
复制代码

打印结果

js naonao 2
css js1 2
node css2 2
end node3
复制代码

从打印结果看,订阅函数的 resolve 值会传递给下一个订阅函数。

打印 hook.promise

打印此时的 hook.promise 方法:

function anonymous(name, age) {
  "use strict";
  var _context;
  var _x = this._x;
  return new Promise((function (_resolve, _reject) {
    var _sync = true;
    function _error(_err) {
      if (_sync)
        _resolve(Promise.resolve().then((function () { throw _err; })));
      else
        _reject(_err);
    };
    function _next1() {
      var _fn2 = _x[2];
      var _hasResult2 = false;
      var _promise2 = _fn2(name, age);
      if (!_promise2 || !_promise2.then)
        throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise2 + ')');
      _promise2.then((function (_result2) {
        _hasResult2 = true;
        if (_result2 !== undefined) {
          name = _result2;
        }
        _resolve(name);
      }), function (_err2) {
        if (_hasResult2) throw _err2;
        _error(_err2);
      });
    }
    function _next0() {
      var _fn1 = _x[1];
      var _hasResult1 = false;
      var _promise1 = _fn1(name, age);
      if (!_promise1 || !_promise1.then)
        throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
      _promise1.then((function (_result1) {
        _hasResult1 = true;
        if (_result1 !== undefined) {
          name = _result1;
        }
        _next1();
      }), function (_err1) {
        if (_hasResult1) throw _err1;
        _error(_err1);
      });
    }
    var _fn0 = _x[0];
    var _hasResult0 = false;
    var _promise0 = _fn0(name, age);
    if (!_promise0 || !_promise0.then)
      throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
    _promise0.then((function (_result0) {
      _hasResult0 = true;
      if (_result0 !== undefined) {
        name = _result0;
      }
      _next0();
    }), function (_err0) {
      if (_hasResult0) throw _err0;
      _error(_err0);
    });
    _sync = false;
  }));

}
复制代码

从代码中可以看出,订阅函数的 resolve 值如果不是 undefined 会赋值给 name,然后再调用下一个订阅函数。

结语

本篇是 Tapable 所有的 Hook 的应用示例。

通过示例可以很好的了解各个 Hook 的特性和用法。同时打印出各个 Hook 的调用方法,能帮助我们更好的了解,它们内部的真正执行逻辑。

下一篇是有关 Interception 拦截器的内容。

更多精彩,请关注微信公众号:闹闹前端

文章分类
前端
文章标签