【前端热知识】一道有意思的手写题

159 阅读3分钟

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();
    },
  };
}

image.png

很遗憾是不可以的,这时候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]);
  });
});

image.png

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']);
  });

image.png

结语

你学废了吗?然后你有更好的答案,欢迎在底下留言。

源码地址