# 前言
咱们书接上回同步方法的实现继续探索下异步方法是如何实现的。
# AsyncParallerHook:异步并行的钩子
AsyncParallerHook.js 文件
// 异步并行的钩子
class AsyncParallerHook {
constructor(args) {
this.tasks = [];
};
/**
* 注册监听函数
* @param {String} name : 名称
* @param {Function} task : 任务函数
*/
tapAsync(name, task) {
this.tasks.push(task);
};
/**
* 执行注册的函数
* @param {...any} args
*/
callAsync(...args) {
// 取出最终的函数
let finalCallback = args.pop();
// 已执行的函数的个数
let index = 0;
// 判断所有注册函数是否已经全部执行完成
let done = () => {
index++;
if (index === this.tasks.length) {
finalCallback(); // z执行最终的回调函数
}
}
// 遍历执行注册函数,并把回调函数转进去
this.tasks.forEach(task => {
task(...args, done)
})
}
}
module.exports = { AsyncParallerHook };
这里this.tasks.forEach
会循环执行每一个函数,并且在函数执行完成之后都会调用一个处理函数done
来判断所有注册函数是否全部执行完成。
如果所有注册函数全部执行完成即index === this.tasks.length
时,则调用最终的回调函数finalCallback()
去处理相应的逻辑。
还是以模拟学习课程为例,代码如下
Lesson.js 文件
const { AsyncParallerHook } = require('./Tapable/AsyncParallerHook');
// 模拟学习的课程
class Lesson {
constructor() {
this.hooks = {
test: new AsyncParallerHook(['name'])
}
};
// 注册钩子
tapAsync() {
this.hooks.test.tapAsync('Vue', function(name, cb) {
setTimeout(_ => {
console.log('Vue', name)
cb()
}, 1000)
})
this.hooks.test.tapAsync('Html', function(name, cb) {
setTimeout(_ => {
console.log('Html', name)
cb()
}, 1000)
})
};
// 启动钩子
start() {
this.hooks.test.callAsync('zp', function() {
console.log('都执行完了')
});
};
}
let lesson = new Lesson();
lesson.tapAsync(); // 注册钩子/事件
lesson.start(); // 启动钩子
输出的结果是:Vue zp Html zp 都执行完了
如上:在注册函数里调用AsyncParallerHook
的实例方法tapAsync
时,里面是用setTimeout
来模拟的异步。而这里的cb
就是AsyncParallerHook
的实例方法callAsync
里的done
函数,即每次执行完都判断一次所有注册函数是否全部完成。
如果注册全部执行完成时,会调用start
启动钩子的函数里的传入的回调函数,也就是对应的AsyncParallerHook
的实例方法callAsync
里取出的finalCallback
函数。
处理这种方式实现,还有另外一种利用promise的实现方式
AsyncParallerHookPromise.js 文件
// 异步并行的钩子
class AsyncParallerHookPromise {
constructor(args) {
this.tasks = [];
};
/**
* 注册监听函数
* @param {String} name : 名称
* @param {Function} task : 任务函数
*/
tapPromise(name, task) {
this.tasks.push(task);
};
/**
* 执行注册的函数
* @param {...any} args
*/
promise(...args) {
// 依次循环并拿到每次的 promise
let tasks = this.tasks.map(task => task(...args));
// 交给 promise.all 执行并返回
return Promise.all(tasks);
}
}
module.exports = { AsyncParallerHookPromise };
通过map
遍历拿到每一个注册函数的Promise
并放在数组里,最后交给Promise.all
去执行,最后返回这个Promise.all
的结果;
Lesson.js 文件
const { AsyncParallerHookPromise } = require('./Tapable/AsyncParallerHookPromise');
// 模拟学习的课程
class Lesson {
constructor() {
this.hooks = {
test: new AsyncParallerHookPromise(['name'])
}
};
// 注册钩子
tapAsync() {
this.hooks.test.tapPromise('Vue', function(name) {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('Vue', name)
resolve()
}, 1000)
})
})
this.hooks.test.tapPromise('Html', function(name) {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('Html', name)
resolve()
}, 1000)
})
})
};
// 启动钩子
start() {
this.hooks.test.promise('zp').then(function() {
console.log('都执行完了')
});
};
}
let lesson = new Lesson();
lesson.tapAsync(); // 注册钩子/事件
lesson.start(); // 启动钩子
如上:在注册函数tapAsync
里是返回一个new Promise
对象,因此才能在AsyncParallerHookPromise
实例方法tapPromise
里通过map
遍历拿到Promise
数组。
在启动函数start
里调用promise
方法通过.then
的方式去获得结果,是因为在AsyncParallerHookPromise
实例方法promise
里最终也是通过Promise.all
方式返回了一个Promise
对象。
# AsyncSeriesHook:异步串行
AsynSeriseHook.js 文件
// 异步串行的钩子
class AsynSeriseHook {
constructor(args) {
this.tasks = [];
};
/**
* 注册监听函数
* @param {String} name : 名称
* @param {Function} task : 任务函数
*/
tapAsync(name, task) {
this.tasks.push(task);
};
/**
* 执行注册的函数
* @param {...any} args
*/
callAsync(...args) {
let finalCallback = args.pop();
let index = 0;
let next = () => {
// 这里是递归,注意结束条件防止溢出
if (index === this.tasks.length) {
finalCallback();
return;
}
let task = this.tasks[index++];
task(...args, next)
}
next();
}
}
module.exports = { AsynSeriseHook };
利用递归的形式默认先执行一次next()
且index = 0
,这样就得到了第一次执行的结果。执行完成后index++
接着执行下一次,以此类推,直到全部执行完成后在去执行finalCallback
函数去处理完成后的逻辑。
const { AsynSeriseHook } = require('./Tapable/AsynSeriseHook');
// 模拟学习的课程
class Lesson {
constructor() {
this.hooks = {
test: new AsynSeriseHook(['name'])
}
};
// 注册钩子
tapAsync() {
this.hooks.test.tapAsync('Vue', function(name, cb) {
setTimeout(_ => {
console.log('Vue', name)
cb()
}, 1000)
})
this.hooks.test.tapAsync('Html', function(name, cb) {
setTimeout(_ => {
console.log('Html', name)
cb()
}, 1000)
})
};
// 启动钩子
start() {
this.hooks.test.callAsync('zp', function() {
console.log('都执行完了')
});
};
}
let lesson = new Lesson();
lesson.tapAsync(); // 注册钩子/事件
lesson.start(); // 启动钩子
如上:通过setTimeout
模拟了异步,并每次执行完成后都执行了回调函数cb
。这里的cb
就是AsynSeriseHook
实例方法callAsync
里的next
函数,于是就完成了串行。
待所有注册函数执行完成,就会调用callAsync
里的回调函数去处理完成逻辑,而这里的回调函数就是AsynSeriseHook
实例方法callAsync
里的finalCallback
函数。
如你所想,还有另一种 Promise 的实现方式
AsyncSeriesHookPromise.js 文件
// 异步串行的钩子
class AsyncSeriesHookPromise {
constructor(args) {
this.tasks = [];
};
/**
* 注册监听函数
* @param {String} name : 名称
* @param {Function} task : 任务函数
*/
tapPromise(name, task) {
this.tasks.push(task);
};
/**
* 执行注册的函数
* @param {...any} args
*/
promise(...args) {
let [first, ...others] = this.tasks;
return others.reduce((pre, task) => {
return pre.then(res => task(...args));
}, first(...args))
}
}
module.exports = { AsyncSeriesHookPromise };
首先会取出第一个注册函数先执行,并通过reduce
循环遍历剩下的所有注册函数。遍历时通过.then
的方式拿到前一次的执行结果,并在拿到结果后接着执行下一个,以此类推形成串行。
Lesson.js 文件
const { AsyncSeriesHookPromise } = require('./Tapable/AsyncSeriesHookPromise');
// 模拟学习的课程
class Lesson {
constructor() {
this.hooks = {
test: new AsyncSeriesHookPromise(['name'])
}
};
// 注册钩子
tapAsync() {
this.hooks.test.tapPromise('Vue', function(name) {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('Vue', name)
resolve()
}, 1000)
})
})
this.hooks.test.tapPromise('Html', function(name) {
return new Promise((resolve, reject) => {
setTimeout(_ => {
console.log('Html', name)
resolve()
}, 1000)
})
})
};
// 启动钩子
start() {
this.hooks.test.promise('zp').then(function() {
console.log('都执行完了')
});
};
}
let lesson = new Lesson();
lesson.tapAsync(); // 注册钩子/事件
lesson.start(); // 启动钩子
如上:在注册钩子tapAsync
里注册函数时,是返回了一个new Promise
对象,所以可在对应的AsyncSeriesHookPromise
实例方法tapPromise
里可通过.then
的方式拿到上一次执行的结果,在拿到结果后紧接着执行下一次。
# AsynSeriseHookWaterfall:异步串行的瀑布的钩子
AsynSeriseHookWaterfall.js 文件
// 异步串行的瀑布的钩子
class AsynSeriseHookWaterfall {
constructor(args) {
this.tasks = [];
};
/**
* 注册监听函数
* @param {String} name : 名称
* @param {Function} task : 任务函数
*/
tapAsync(name, task) {
this.tasks.push(task);
};
/**
* 执行注册的函数
* @param {...any} args
*/
callAsync(...args) {
let finalCallback = args.pop();
let index = 0;
let next = (err, data) => {
let task = this.tasks[index];
// 这里是递归,注意结束条件防止溢出
if (!task || err) return finalCallback();
if (index == 0) {
task(...args, next)
} else {
task(data, next)
}
index++;
}
next();
}
}
module.exports = { AsynSeriseHookWaterfall };
在是实现next
函数的时候接受两个参数,分别是:
err
:上次执行的结果是否发生错误,非null
即可视为错误。
data
:需要传入下一次的数据。
默认先执行一次,既然是递归就要判断结束条件防止溢出。当拿不到当前注册函数时即index
大于了tasks.length
时(!task == true
)或上个一注册函数发生错误时(err == true
),都要终端当前的递归并执行最终的函数finalCallback
去处理逻辑。
这里需要注意的是当index == 0
时即第一次执行时,拿到的参数是args
,当index
大于0
即不是第一次执行时,参数是上一次注册函数传递过来的即next
函数的data
参数。
Lesson.js 文件
const { AsynSeriseHookWaterfall } = require('./Tapable/AsynSeriseHookWaterfall');
// 模拟学习的课程
class Lesson {
constructor() {
this.hooks = {
test: new AsynSeriseHookWaterfall(['name'])
}
};
// 注册钩子
tapAsync() {
this.hooks.test.tapAsync('Vue', function(name, cb) {
setTimeout(_ => {
console.log('Vue', name)
cb(null, 'Vue 可以了')
}, 1000);
})
this.hooks.test.tapAsync('Html', function(name, cb) {
setTimeout(_ => {
console.log('Html', name)
cb()
}, 1000);
})
};
// 启动钩子
start() {
this.hooks.test.callAsync('zp', function() {
console.log('都执行完了')
});
};
}
let lesson = new Lesson();
lesson.tapAsync(); // 注册钩子/事件
lesson.start(); // 启动钩子
如上:在第一个注册函数里cb
传了null
和Vue 可以了
时,表明此次注册函数执行成功,可以执行下一次,并把Vue 可以了
传给了下一次的注册函数。也就是AsynSeriseHookWaterfall
实例方法callAsync
里的next
函数的err==null,data == 'Vue 可以了'
,所以输出结果是:Vue zp Html Vue 可以了都执行完了
。
this.hooks.test.tapAsync('Vue', function(name, cb) {
setTimeout(_ => {
console.log('Vue', name)
cb('哇哦,出错了', 'Vue 可以了')
}, 1000);
})
这时cb
传的第一个参数不是null
了,即AsynSeriseHookWaterfall
实例方法callAsync
里的next
函数的err != null
,所以语句(!task || err)
为true
了,这时要终止执行递归函数,并执行finalCallback
函数来处理所需逻辑。
如你所想,还有另一种Promise
的实现方式
AsynSeriseHookWaterfallPromise.js 文件
// 异步串行并行的钩子
class AsynSeriseHookWaterfallPromise {
constructor(args) {
this.tasks = [];
};
/**
* 注册监听函数
* @param {String} name : 名称
* @param {Function} task : 任务函数
*/
tapPromise(name, task) {
this.tasks.push(task);
};
/**
* 执行注册的函数
* @param {...any} args
*/
promise(...args) {
let [first, ...others] = this.tasks;
return others.reduce((pre, task, index) => {
return pre.then(res => task(res));
}, first(...args))
}
}
module.exports = { AsynSeriseHookWaterfallPromise };