xxleyi / learning_list

聚集自己的学习笔记
10 stars 3 forks source link

异步任务的串行控制 #251

Open xxleyi opened 3 years ago

xxleyi commented 3 years ago

看到一个有趣的面试题:https://github.com/sl1673495/blogs/issues/55

据说是蚂蚁金服的面试题,实现起来确实有坑,在参考了已有的两个解法之后,我理清了题意,找到了或许是最简易的一种解法。

题目:

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

分析:题目的本质问题是,扔过来一堆嵌套的 effects,实现 run 方法,将这些 effects 以异步串行的方式执行完毕。同时,run 方法会接受单个 effects 或者回调函数,flow 执行完毕之后开始执行。

那我们就要想想,JS 中甚至抛开具体语言来说,异步串行的编程方案是什么?

或许是最佳的这个解法恰好使用了 JS 中最新的编程能力:async await + 生成器

其中:生成器可以解决嵌套问题,async await 完美契合逻辑上同步,执行上异步异步串行

代码:

function Flow(flow) {
  this.flow = flow
}

async function _run(effects) {
  for (const effect of effects) {
    await effect()
  }
}

const genEffects = function* (flow) {
  for (const e of flow) {
    if (Array.isArray(e)) {
      yield* genEffects(e)
    } else if (e instanceof Flow) {
      yield* genEffects(e.flow)
    } else {
      yield e
    }
  }
}

Flow.prototype.run = function (done) {
  _run(genEffects(this.flow)).then(
    () => {
      if (typeof done === 'function') done()
    }
  )
}

function createFlow(flow) {
  return new Flow(flow)
}

const log = console.log
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 的顺序打印