分享一道关于Promise编排的面试题

95 阅读1分钟

背景

前几天有幸参加了我梦寐已久的公司的技术三面,其中一道题目想和大家分享下

题目

有一个asyncSum函数可以异步求两数之和,请实现一个sum函数,实现求和功能

// 类似这种,通过asyncSum函数异步拿到两数之和
function asyncSum(a, b, cb) {
    setTimeout(() => {
       cb(a + b)
    })
}

const res1 = await sum(1,2,3,4)
const res2 = await sum(1,2,3,4,5,6)

分析

1.最好将asyncSum这种类似node callback调用的方式,转换成Promise(好处:可以利用async await写出类似同步代码,使代码阅读性更高)

const asyncSumPromise = (a, b) => {
  if (a === 0) return Promise.resolve(b);
  if (b === 0) return Promise.resolve(a);
  return new Promise((resolve) => {
    asyncSum(a, b, resolve)
  });
};

其实还能加上根据参数,缓存结果

  1. 拿到所有入参,两个两个调用asyncSumPromise,然后利用Promise.all处理,得到结果,然后递归调用sum
const sum = async (...args) => {
  // 终止递归
  if (args.length <= 2) return asyncSumPromise(args[0] || 0, args[1] || 0);
  const len = args.length;
  let i = 0;
  let taskList = [];

  // 初始化任务
  while (i < len) {
    taskList.push(asyncSumPromise(args[i], args[i + 1] || 0));
    i += 2;
  }

  const res = await Promise.all(taskList);
  // 递归求和
  return sum(...res);
};

新挑战

当我开开心心的写完后,面试官又说,如果现在出现一种场景,假设我某次调用asyncSum很慢,然后你又使用了Promise.all,这就会导致所有的都堵塞住,请问有什么办法能解决这种场景吗?

再次分析

出现这种场景的原因是Promise.all的特性导致的(需要所有Promise都resolve才行),那其实可以使用Promise.race,每当完成两个后,然后迭代的继续求和,此时就解决了这个场景。

const sum = async (...args) => {
  if (args.length <= 2) return asyncSumPromise(args[0] || 0, args[1] || 0);
  const len = args.length;
  let i = 0;
  let taskList = [];

  // 初始化任务
  while (i < len) {
    // 注意:这里需要保存一下值的引用
    let pushIndex = i >> 1;
    taskList.push(
      // 注意:这里需要记录,当前的Promise的索引,用于完成后清除
      asyncSumPromise(args[i], args[i + 1] || 0).then((v) => [v, pushIndex])
    );
    i += 2;
  }

  // 清除完成的
  const clearComplete = (completeIndex) => {
    taskList = taskList.map((item, listIndex) =>
      // 注意:这里需要做一个索引的变动
      listIndex > completeIndex ? item.then(([v, i]) => [v, i - 1]) : item
    );
    taskList.splice(completeIndex, 1);
  };

  // 得到最快的
  const handle = async () => {
    const [v, index] = await Promise.race(taskList);
    clearComplete(index);
    return v;
  };

  // 处理最快两个
  while (taskList.length) {
    const v1 = await handle();
    // 任务清空完
    if (taskList.length === 0) return v1;
    const v2 = await handle();
    taskList.push(
      asyncSumPromise(v1, v2).then((v) => [v, taskList.length - 1])
    );
  }
};

哈哈哈,题目就解决了

最后

很荣幸能有这次面试机会,去认清楚自己的不足。

也祝各位小伙伴们,能早日拿到自己心仪Offer。各位面试失败的小伙伴们也不要气馁,毕竟失败总是贯穿人生的,早日重拾信心,加油,你是最棒的,哈哈哈。