深入解析价值25k的蚂蚁金服异步串行面试题

简介: 注:代码有错,请阅读原文。朋友去面试蚂蚁金服,遇到了一道面试题,乍一看感觉挺简单的,但是实现起来发现内部值得一提的点还是挺多的。

前言


注:代码有错,请阅读原文。朋友去面试蚂蚁金服,遇到了一道面试题,乍一看感觉挺简单的,但是实现起来发现内部值得一提的点还是挺多的。

先看题目:

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const subFlow = createFlow([() => delay(1000).then(() => log("c"))]);
createFlow([
  () => log("a"),
  () => log("b"),
  subFlow,
  [() => delay(1000).then(() => log("d")), () => log("e")],
]).run(() => {
  console.log("done");
});
// 需要按照 a,b,延迟1秒,c,延迟1秒,d,e, done 的顺序打印

按照上面的测试用例,实现 createFlow

  • flow 是指一系列 effects 组成的逻辑片段。
  • flow 支持嵌套。
  • effects 的执行只需要支持串行。

分析


先以入参分析,createFlow 接受一个数组作为参数(按照题意里面的每一项应该叫做 effect),排除掉一些重复的项,我们把参数数组中的每一项整理归类一下,总共有如下几种类型:

  1. 普通函数:
() => log("a");
  1. 延迟函数(Promise):
() => delay(1000).then(() => log("d"));
  1. 另一个 flow
const subFlow = createFlow([() => delay(1000).then(() => log("c"))]);
  1. 用数组包裹的上述三项。

实现


先把参数浅拷贝一份(编写库函数,尽量不要影响用户传入的参数是个原则),再简单的扁平化 flat 一下。(处理情况 4)

function createFlow(effects = []) {
  let sources = effects.slice().flat();
}

观察题意,createFlow 并不会让方法开始执行,需要 .run() 之后才会开始执行,所以先定义好这个函数:

function createFlow(effects = []) {
  let sources = effects.slice().flat();
  function run(callback) {
    while (sources.length) {
      const task = sources.shift();
    }
    callback?.();
  }
}

这里我选择用 while 循环依次处理数组中的每个 effect,便于随时中断。

对于函数类型的 effect,直接执行它:

function createFlow(effects = []) {
  let sources = effects.slice().flat();
  function run(callback) {
    while (sources.length) {
      const task = sources.shift();
      if (typeof task === "function") {
        const res = task();
      }
    }
    // 在所有任务执行完毕后 执行传入的回调函数
    callback?.();
  }
  return {
    run,
    isFlow: true,
  };
}

这里拿到了函数的返回值 res,有一个情况别忘了,就是 effect 返回的是一个 Promise,比如这种情况:

() => delay(1000).then(() => log("d"));

那么拿到返回值后,这里直接简化判断,看返回值是否有 then 属性来判断它是否是一个 Promise(生产环境请选择更加严谨的方法)。

if (res?.then) {
  res.then(createFlow(sources).run);
  break;
}

这里我选择中断本次的 flow 执行,并且用剩下的 sources 去建立一个新的 flow,并且在上一个 Promise 的 then 方法里再去异步的开启新的 flowrun

这样,上面延迟 1s 后的 Promise 被 resolve 之后,剩下的 sources 任务数组会被新的 flow.run() 驱动,继续执行。

接下来再处理 effect 是另一个 flow 的情况,注意上面编写的大致函数体,我们已经让 createFlow 这个函数返回值带上 isFlow 这个标记,用来判断它是否是一个 flow

if (typeof task === "function") {
  const res = task();
  if (res?.then) {
    res.then(createFlow(sources).run);
    break;
  }
} else if (task?.isFlow) {
  task.run(createFlow(sources).run);
  break;
}

else if 的部分,直接调用传入的 flowrun,把剩下的 sources 创建的新的 flowrun 方法作为传入 flowcallback 回调。

这样,参数中传入的 flow 执行完毕后,才会继续执行剩下的任务。


完整代码


function createFlow(effects = []) {
  let sources = effects.slice().flat();
  function run(callback) {
    while (sources.length) {
      const task = sources.shift();
      if (typeof task === "function") {
        const res = task();
        if (res?.then) {
          res.then(createFlow(sources).run);
          break;
        }
      } else if (task?.isFlow) {
        task.run(createFlow(sources).run);
        break;
      }
    }
    // 在所有任务执行完毕后 执行传入的回调函数
    callback?.();
  }
  return {
    run,
    isFlow: true,
  };
}
const delay = () => new Promise((resolve) => setTimeout(resolve, 1000));
createFlow([
  () => console.log("a"),
  () => console.log("b"),
  createFlow([() => console.log("c")]),
  [() => delay().then(() => console.log("d")), () => console.log("e")],
]).run();


总结


这道面试题主要的目的是考察对于异步串行流的控制,巧妙的利用自身的递归设计来处理传入的参数也是一个 flow的情况,在编写题目的过程中展示你对 Promise 的熟练运用,一定会让面试官对你刮目相看的~

祝大家在大环境不好的情况下,都能拿到自己满意的 offer,加油。

相关文章
|
2月前
|
算法 测试技术 数据处理
【C/C++ 面试技巧】如何在简单的项目里突出自己的价值?
【C/C++ 面试技巧】如何在简单的项目里突出自己的价值?
50 1
|
2月前
|
存储 并行计算 前端开发
【C++ 函数 基础教程 第五篇】C++深度解析:函数包裹与异步计算的艺术(二)
【C++ 函数 基础教程 第五篇】C++深度解析:函数包裹与异步计算的艺术
39 1
|
2月前
|
数据安全/隐私保护 C++ 容器
【C++ 函数 基础教程 第五篇】C++深度解析:函数包裹与异步计算的艺术(一)
【C++ 函数 基础教程 第五篇】C++深度解析:函数包裹与异步计算的艺术
49 0
|
3天前
|
并行计算 数据处理 开发者
Python并发编程:解析异步IO与多线程
本文探讨了Python中的并发编程技术,着重比较了异步IO和多线程两种常见的并发模型。通过详细分析它们的特点、优劣势以及适用场景,帮助读者更好地理解并选择适合自己项目需求的并发编程方式。
|
3天前
|
JavaScript 大数据 开发者
Node.js的异步I/O模型与事件循环:深度解析
【4月更文挑战第29天】本文深入解析Node.js的异步I/O模型和事件循环机制。Node.js采用单线程与异步I/O,遇到I/O操作时立即返回并继续执行,结果存入回调函数队列。事件循环不断检查并处理I/O事件,通过回调函数通知结果,实现非阻塞和高并发。这种事件驱动编程模型简化了编程,使开发者更专注业务逻辑,为高并发场景提供高效解决方案。
|
12天前
|
数据采集 机器学习/深度学习 数据挖掘
Python数据清洗与预处理面试题解析
【4月更文挑战第17天】本文介绍了Python数据清洗与预处理在面试中的常见问题,包括Pandas基础操作、异常值处理和特征工程。通过示例代码展示了数据读取、筛选、合并、分组统计、离群点检测、缺失值和重复值处理、特征缩放、编码、转换和降维。强调了易错点,如忽视数据质量检查、盲目处理数据、数据隐私保护、过度简化特征关系和忽视模型输入要求。掌握这些技能和策略将有助于在面试中脱颖而出。
25 8
|
15天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
30 0
|
15天前
|
API Python
Python模块化编程:面试题深度解析
【4月更文挑战第14天】了解Python模块化编程对于构建大型项目至关重要,它涉及代码组织、复用和维护。本文深入探讨了模块、包、导入机制、命名空间和作用域等基础概念,并列举了面试中常见的模块导入混乱、不适当星号导入等问题,强调了避免循环依赖、合理使用`__init__.py`以及理解模块作用域的重要性。掌握这些知识将有助于在面试中自信应对模块化编程的相关挑战。
21 0
|
2月前
|
Linux 程序员 C++
【C++ 常见的异步机制】探索现代异步编程:从 ASIO 到协程的底层机制解析
【C++ 常见的异步机制】探索现代异步编程:从 ASIO 到协程的底层机制解析
135 2
|
2月前
|
监控 算法 Unix
【Linux 异步操作】深入理解 Linux 异步通知机制:原理、应用与实例解析
【Linux 异步操作】深入理解 Linux 异步通知机制:原理、应用与实例解析
64 0

推荐镜像

更多