常用的高阶函数之控制执行顺序

目录

  1. 1. 缓存所有结果,然后按顺序执行
  2. 2. 直接执行,忽略不合时序的结果

在日常开发,经常会有要求异步函数的执行结果按顺序执行,看下面的例子:

let asyncF = (i, callback) => {
2// 随机延迟若干事件触发回调
let lateTime = Math.random() * 10;
setTimeout(() => {callback(i)}, lateTime);
}

for (var i = 0; i < 5; i ++) {
asyncF(i, console.log); // 结果完全是随机的,一个可能的结果是0,1,4,3,2
}

这个例子模拟了一系列的异步调用,它们的执行时间不确定,所以完全无法预测多次调用的结果,对于这类操作通常有2种解决方案:

缓存所有结果,然后按顺序执行

如果每个发出的请求返回内容都很有用,而且需要保持一定的顺序,可以使用这种方案。

function sequenceAll(func) {
2// 每次函数调用的唯一 id,调用后 id + 1
let callUid = 0;
// 上次成功调用了 callback 的 id,用于和当前函数的 id 作比较,
// 确定是否需要忽略当前的调用。
let nextCallId = 0;
let taskQueue = [];
// 运行被缓存的数据
const runnerNext = (runId) => {
if (taskQueue[runId]) {
taskQueue[runId]();
// 运行下一个已被缓存的数据
runnerNext(runId + 1);
}
}
return function(...args) {
let currCallId = callUid;
callUid += 1;
taskQueue.push(undefined);
// 要求 func 接收的最后一个参数是一个回调函数,如果不是的话需要先改造成这样的形式
const callback = args.pop();
return func(...args, function(...iargs) {
// 如果回调为当前的任务,则执行
if (currCallId === nextCallId) {
if (callback && typeof callback === 'function') {
callback(...iargs);
}
nextCallId += 1;
runnerNext(nextCallId);
// 否则,放入回调队列,等待正确的顺序被调用
} else {
taskQueue[currCallId] = () => {
callback(...iargs);
nextCallId += 1;
};
}
});
}
}

//示例
let asyncFSeq = sequenceAll(asyncF);
for (var i = 0; i < 50; i ++) {
asyncFSeq(i, console.log); // 结果必然是 0 1 2 3 4
}

直接执行,忽略不合时序的结果

如果在执行异步操作拿到结果准备回调后发现已经有晚于其发出的异步操作的回调已经被执行,name 忽略本函数的调用结果。

function sequenceIgnore(func) {
2// 每次函数调用的唯一 id,调用后 id + 1
let callUid = 0;
// 上次成功调用了 callback 的 id,用于和当前函数的 id 作比较,
// 确定是否需要忽略当前的调用。
let lastCallId = 0;
return function(...args) {
let currCallId = callUid;
callUid += 1;
const callback = args.pop()
// 要求 func 接收的最后一个参数是一个回调函数,如果不是的话需要先改造成这样的形式
return func(...args, function(...args) {
if (currCallId < lastCallId) {
return;
}
lastCallId = currCallId;
if (callback && typeof callback === 'function') {
callback(...iargs);
}
});
}
}

//示例
const asyncFSeq = sequenceIgnore(asyncF);
// 此时,结果虽然也是随机的,但是每次执行的结果必然是逐个递增的,
// 后发出的异步操作不会被先发出的操作覆盖,即最后一个必然为 4
for (var i = 0; i < 5; i ++) {
asyncFSeq(i, console.log); // 结果也是随机的,一个可能的输出 0 3 4
}

这种处理方法更适合于处理只需要最晚发出的执行结果的操作。比如根据用户输入内容实时发出请求进行模糊匹配,这个时候呈现给用户的自然是与用户最后输入的文本匹配的结果是最好的处理方案。

知识共享许可协议 知识共享许可协议 知识共享许可协议 本网站原创内容(非转载文章)采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。