Skip to content

链式调用和延迟执行

Published:

前言

假设我们有一个函数 arrange,它可以按我们想要的顺序执行一些操作。比如:

function arrange(name: string) {}

arrange("William").execute();
// > William is notified

arrange("William").do("commit").execute();
// > William is notified
// > Start to commit

arrange("William").wait(5).do("commit").execute();
// > William is notified
// 等待5秒
// > Start to commit

arrange("William").waitFirst(5).do("push").execute();
// > 等待5秒
// > William is notified
// > Start to push

这个函数有几个特点:

  1. 可以链式调用(一个接一个地调用方法)。
  2. 可以延迟执行某些操作。
  3. 只有调用 execute() 方法时,才会按顺序执行所有操作。

思路

首先我们可以看到,无论是函数本身,还是其他链式方法,函数本身不会引发什么执行,而只有等到最后调用 execute() 的时候才会执行。所以,无论是函数本身还是 do 还是 waiit 还是其他链式方法也好,它只是把一个任务加到某个 队列 里面去等待,等着它调用 execute 函数,再去把这个队列循环一遍执行。

所以,这个函数本身应该有一个队列并且自身需要往队列添加任务:

function arrange(name) {
  const tasks = [];
  tasks.push(() => {
    console.log(`${name} is notfied`);
  });
}

但我们需要考虑到后续还有其他方法的调用,所以这个函数需要返回一个对象,这个对象包含多个可以被链式调用的方法:

function arrange(name) {
  ...

  /* chain methods */
  function doSomething(action: string) {}
  function wait(sec) {}
  function waitFirst(sec) {}
  function execute() {}

  return {
    do: doSomething,
    wait,
    waitFirst,
    execute
  }
}

接着,我们继续填充几个链式方法的内容:

function arrange(name) {
  const tasks = []
  tasks.push(() => {
    console.log(`${name} is notfied`
  })

  function doSeomthing(action) {
    tasks.push(() => {
      console.log(`Start to ${action}`)
    })
  }

  function wait(sec) {
    tasks.push(() => new Promise(resolve => {
      setTimeout(resolve, sec * 1000)
    }))
  }

  function waitFirst(sec) {
		tasks.push(() => new Promise((resolve) => {
      setTimeout(resolve, sec * 1000)
    }))
  }

  function execute() {}

  return {
    do: doSeomthing,
    wait,
    waitFirst,
    execute
  }
}

我们根据执行结果发现,当调用waitFirst 时它总会在第一个时候执行,所以,我们需要让它放在队列的第一项:

function waitFirst(sec) {
  tasks.unshift((() => {
    ...
  }))
}

接着,execute 方法会将队列中所有的任务都拿出来执行一遍:

async function execute() {
  for (const task of tasks) {
    await task();
  }
}

可写到这里并不能够满足需求,因为当我们执行了 do 或者 wait 等方法,我们在这后面还能接着调用其他链式方法,所以,这说明每一个函数执行完了之后,都需要再将链式对象返回:

function doSomething(action) {
  ...
  return this
}
function wait(sec) {
  ...
  return this
}

这里,我们使用了 this 关键字,将整个 arrange 上下文返回,这样就不用重新赋值一个变量,并且返回this 能够让所有链式方法都拥有函数上下文,都可以使用链式调用。

完整代码

type Task = () => Promise<void> | void;

interface Arrange {
  do: (action: string) => Arrange;
  wait: (seconds: number) => Arrange;
  waitFirst: (seconds: number) => Arrange;
  execute: () => Promise<void>;
}

function arrange(name: string): Arrange {
  const tasks: Task[] = [];

  tasks.push(() => {
    console.log(`${name} is notified`);
  });

  const api: Arrange = {
    do(action: string) {
      tasks.push(() => {
        console.log(`Start to ${action}`);
      });
      return this;
    },
    wait(seconds: number) {
      tasks.push(() => {
        return new Promise<void>(resolve => {
          setTimeout(resolve, seconds * 1000);
        });
      });
      return this;
    },
    waitFirst(seconds: number) {
      tasks.unshift(() => {
        return new Promise<void>(resolve => {
          setTimeout(resolve, seconds * 1000);
        });
      });
      return this;
    },
    async execute() {
      for (const task of tasks) {
        await task();
      }
    },
  };

  return api;
}

// 测试代码
arrange("William").waitFirst(2).do("commit").wait(3).do("push").execute();

参考资料