Flow
题目描述:
实现一个flow函数,最后可以按照以下输出
* -> 1
* -> 2
* [延迟1秒]
* -> 3
* [延迟1秒]
* -> 4
* -> 5
* -> 6
* -> done
const stop = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const subFlow = flow([
() => stop(1000).then(() => console.log('3')),
]);
export function flow(list: any[]) {
// ...
}
flow([
() => console.log('1'),
() => console.log('2'),
subFlow,
[() => stop(1000).then(() => console.log('4')), () => console.log('5')],
() => console.log('6'),
]).run(() => {
console.log('done');
});
0、分析
我们先来分析,flow会返回一个run函数,这个函数可以执行;它可以接受一个数组,数组里面元素的形式有三种,一种是正常的函数,一种是flow返回的某个东西,最后一种还是数组。
1、实现处理正常函数和返回run函数
describe('flow', () => {
it('should receiver normal function list and return function "run"', () => {
const result: number[] = [];
flow([
() => result.push(1),
() => result.push(2),
]).run(() => result.push(100));
expect(result).toEqual([1, 2, 100]);
});
});
type Entry = Function | Array<Entry> | Output;
interface Output { run: (done: Function) => void };
const isReturnFlow = (target: Entry): target is Output => Object.hasOwn(target, 'run');
export function flow(list: Entry[]): Output {
for (const item of list) {
if (Array.isArray(item)) {
// 对应数组的情况
}
else if (isReturnFlow(item)) {
// 对应传入flow处理后的情况
}
else {
// 对应正常的函数情况
item();
}
}
return {
run(done: Function) {
done();
},
};
}
2、实现可以延迟输出
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('should can delay execute function', () => {
const stop = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const result: number[] = [];
flow([
() => result.push(1),
() => stop(1000).then(() => result.push(3)),
() => result.push(2),
]).run(() => result.push(100));
// 这时候因为stop而被暂停
expect(result).toEqual([1]);
vi.advanceTimersToNextTimer();
expect(result).toEqual([1, 3, 2, 100]);
});
这时候我们很自然的想到,如果我们可以使用 await 等到它执行完就好了。那么怎么加呢?直接改成async吗?
错误版本1
export async function flow(list: Entry[]): Output {
for (const item of list) {
if (Array.isArray(item)) {
}
else if (isReturnFlow(item)) {
}
else {
await item();
}
}
return {
run(done: Function) {
done();
},
};
}
很遗憾是不可以的,这时候flow返回的是一个pending状态的Promise
所以我想到的正确的做法是用函数把内部逻辑包一层,在run的时候等它执行完再去调用done
type Entry = Function | Array<Entry> | Output;
interface Output { run: (done: Function) => Promise<void> };
const isReturnFlow = (target: Entry): target is Output => Object.hasOwn(target, 'run');
export function flow(list: Entry[]): Output {
const wrapper = async () => {
for (const item of list) {
if (Array.isArray(item)) {
// ...
}
else if (isReturnFlow(item)) {
// ...
}
else {
await item();
}
}
};
return {
async run(done: Function) {
await wrapper();
done();
},
};
}
另外,因为我们改成了异步的输出,测试用例也需要相应的做修改
describe('flow', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('should receiver normal function list and return function "run"', async () => {
const result: number[] = [];
flow([
() => result.push(1),
() => result.push(2),
]).run(() => result.push(100));
await vi.advanceTimersToNextTimerAsync();
expect(result).toEqual([1, 2, 100]);
});
it('should can delay execute function', async () => {
const stop = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const result: number[] = [];
flow([
() => result.push(1),
() => stop(1000).then(() => result.push(3)),
() => result.push(2),
]).run(() => result.push(100));
expect(result).toEqual([1]);
await vi.advanceTimersToNextTimerAsync();
expect(result).toEqual([1, 3, 2, 100]);
});
});
3、处理flow返回的对象
it('should can handle object which is generated by "flow"', async () => {
const result: number[] = [];
const stop = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const subFlow = flow([
() => stop(1000).then(() => result.push(3)),
]);
flow([
() => result.push(1),
() => result.push(2),
subFlow]).run(() => result.push(100));
expect(result).toEqual([1]);
await vi.advanceTimersToNextTimerAsync();
expect(result).toEqual([1, 2, 3, 100]);
});
其实很简单,我们只需要简单的调用它底下的run方法等他执行完即可
type Entry = Function | Array<Entry> | Output;
interface Output { run: (done?: Function) => Promise<void> };
const isReturnFlow = (target: Entry): target is Output => Object.hasOwn(target, 'run');
export function flow(list: Entry[]): Output {
const wrapper = async (arr: Entry[]) => {
for (const item of arr) {
if (Array.isArray(item)) {
// ...
}
else if (isReturnFlow(item)) {
// ...
await item.run();
}
else {
await item();
}
}
};
return {
async run(done?: Function) {
await wrapper(list);
done?.();
},
};
}
4、处理数组
it('should can handle Array', async () => {
const result: number[] = [];
const stop = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
flow([
() => result.push(1),
[() => stop(1000).then(() => result.push(4)), () => result.push(5)],
() => result.push(2),
]).run(() => result.push(100));
expect(result).toEqual([1]);
await vi.advanceTimersToNextTimerAsync();
expect(result).toEqual([1, 4, 5, 2, 100]);
});
数组的话,其实就是其他两种情况的结合体,再用flow跑一遍就行了
export function flow(list: Entry[]): Output {
const wrapper = async (arr: Entry[]) => {
for (const item of arr) {
if (Array.isArray(item))
await flow(item).run();
else if (isReturnFlow(item))
await item.run();
else
await item();
}
};
return {
async run(done?: Function) {
await wrapper(list);
done?.();
},
};
}
5、完整的过一次用例
it('should pass all cases', async () => {
const resultLog: string[] = [];
vi.spyOn(console, 'log').mockImplementation((string) => {
resultLog.push(string);
});
const stop = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const subFlow = flow([
() => stop(1000).then(() => console.log('3')),
]);
flow([
() => console.log('1'),
() => console.log('2'),
subFlow,
[() => stop(1000).then(() => console.log('4')), () => console.log('5')],
() => console.log('6'),
]).run(() => {
console.log('done');
});
await vi.advanceTimersToNextTimerAsync();
expect(resultLog).toEqual(['1', '2', '3']);
await vi.advanceTimersToNextTimerAsync();
expect(resultLog).toEqual(['1', '2', '3', '4', '5', '6', 'done']);
});
结语
你学废了吗?然后你有更好的答案,欢迎在底下留言。