Ant Financial Асинхронные Серийные Вопросы Интервью

JavaScript

предисловие

Друг пошел на собеседование в Ant Financial и столкнулся с вопросом для интервью, который на первый взгляд казался очень простым, но, осознав его, я обнаружил, что есть еще немало моментов, о которых стоит упомянуть.

Сначала посмотрите тему:

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. Функция задержки (обещание):
() => 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, чтобы определить, является ли оно обещанием (пожалуйста, выберите более строгий метод для производственной среды).

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

Здесь я решил прервать на этот разflowвыполнить и использовать оставшиесяsourcesсоздать новыйflow, и асинхронно открыть новый в методе then предыдущего промисаflowизrun.

Таким образом, после того, как вышеуказанный Promise отложен на Resolve 1s, оставшиесяsourcesКоличество заданий будет новымflow.run()езжайте и продолжайте.

разобраться со следующимeffectДругойflow, обратите внимание на написанное выше примерное тело функции, мы сделалиcreateFlowЭта функция возвращает значение сisFlowЭтот флаг используется для определения того, является лиflow.

// 把callback放到下一个flow的callback时机里执行
const next = () => createFlow(sources).run(callback)
if (typeof task === "function") {
  const res = task();
  if (res?.then) {
    res.then(next);
    return;
  }
} else if (task?.isFlow) {
  task.run(next);
  return;
}

Смотретьelse ifчасть, которая напрямую вызывает переданныйflowизrun, положить остальноеsourcesсоздал новыйflow, и поместите этот раундcallbackположить в новыйflowизcallbackМесто нахождения. Выполнять после завершения всех задач.

определитьnextметод, используемый при обнаружении асинхронной задачи или другогоflowкогда

Таким образом, переданный параметрflowПосле завершения выполнения мы продолжим реализацию оставшихся задач, а в финальном исполненииcallback.

полный код

function createFlow(effects = []) {
  let sources = effects.slice().flat();
  function run(callback) {
    while (sources.length) {
      const task = sources.shift();
      // 把callback放到下一个flow的callback时机里执行
      const next = () => createFlow(sources).run(callback)
      if (typeof task === "function") {
        const res = task();
        if (res?.then) {
          res.then(next);
          return;
        }
      } else if (task?.isFlow) {
        task.run(next);
        return;
      }
    }
    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В процессе написания вопроса демонстрация того, как вы умело используете обещания, определенно произведет впечатление на интервьюера~

Желаю вам всем получить предложение, которое вас устроит в плохой среде.