[ 腾讯面试题 ] Javascript 2种方式优雅实现需等待的链式调用

107 阅读1分钟

刷到一篇面经: 一个半小时的腾讯一面 - 掘金

里面的第一题,实现一个HardMan函数,实现链式调用。

image.png

初次查看,以为用 async 函数可以轻松拿下,但尝试后发现 async 函数返回的还是Promise,直接链式调用必然会用到 then 。

经过一段时间学习后,总结了2种实现方法:

  1. 使用内置异步链
  2. 使用异步任务队列

1. 内置异步链

利用 Promise.resolve().then 实现阻塞

function HardMan(name) {
  console.log(`I am ${name}`);

  let lastTime = new Date();
  let promiseChain = Promise.resolve(); // 用于管理异步链

  return {
    rest(seconds) {
      promiseChain = promiseChain.then(() => {
        console.log(`(等待 ${seconds} 秒)`);
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve();
          }, seconds * 1000);
        });
      });
      return this; // 保持链式调用
    },
    learn(subject) {
      promiseChain = promiseChain.then(() => {
        const now = new Date();
        const timeDiff = Math.round((now - lastTime) / 1000);
        lastTime = now;
        console.log(`Start learning after ${timeDiff} seconds`);
        console.log(`Learning ${subject}`);
      });
      return this; // 保持链式调用
    },
  };
}

HardMan("Jack").rest(3).learn("JavaScript").rest(2).learn("Vue");

2.异步任务队列

利用 setTimeout((),0) 的特性,在所有同步任务完成后才启动。那么就可以把所有异步 Promise 推入任务队列后再执行。

function HardMan(name) {
  console.log(`I am ${name}`);

  let lastTime = Date.now();
  const tasks = []; // 任务队列

  const obj = {
    rest: function (seconds) {
      console.log("推入1");
      tasks.push(() => {
        console.log(`(等待 ${seconds} 秒)`);
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve();
          }, seconds * 1000);
        });
      });
      return obj;
    },
    learn: function (subject) {
      console.log("推入2");
      tasks.push(() => {
        const curTime = Date.now();
        const timeDiff = Math.round((curTime - lastTime) / 1000);
        lastTime = Date.now();
        console.log(`Start learning after ${timeDiff} seconds`);
        console.log(`Learning ${subject}`);
      });
      return obj;
    },
  };

  // 任务执行器
  console.log("任务执行器触发");
  setTimeout(async () => {
    for (const task of tasks) {
      await task();
    }
  }, 0);

  return obj;
}

HardMan("Jack")
  .learn("computer")
  .rest(1)
  .learn("computer")
  .rest(1)
  .learn("computer");