Рубрики
Без рубрики

Совет – цепь обещает синхронно

На одном проекте моей компании мы столкнулись с нетривиальной проблемой, которую я нахожу, может быть действительно интересной … Теги с JavaScript, функциональным, декларативным.

На одном проекте моей компании мы столкнулись с нетривиальным вопросом, что я считаю, что мне действительно интересно решить вместе. Эта статья в основном даст вам обзор проблемы и о том, как я решил его использовать метод Array.Prototype Уменьшить Анкет

Вот быстрый обзор контекста вокруг моей проблемы.

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

const initialState = { /* Initial state of the state machine */ };

// processCharacter :: (State) -> (character) -> Promise(State)
const processCharacter = (state) => async (character) => {
  // ...asynchronously process the character
  return newState;
};

// (State) -> Promise(Result)
const getResultFromState = (state) => {
  // ...converts the state to a result
  return result;
}

// Function to implement.
// :: string -> Promise(Result)
const processString = async (string) => {
  // ?
  return result
}

Короче говоря, вот другой шаг, учитывая начальную стадию:

  • Разделить строку в массив символов.
  • Для каждого персонаж , позвоните ждут processcharacteracter (символ) (штат) и назначьте его Государство Анкет
  • Позвоните GetResultFromState до последней версии Государство Анкет

TL; DR Перейти прямо к решению, нажав здесь Анкет

Практический пример

Чтобы проиллюстрировать эту проблему, давайте определим практический пример:

// Simulates different processing time based on character.
const calculateTimeout = character => character.charCodeAt(0) * 10;
// Allows to set to use setTimeout with await.
const timeout= async (ms) => new Promise(rs => setTimeout(rs, ms));
// :: string -> [character]
const stringToArray = string => [...string];

const initialState = { stringProcessed: '' };

const processCharacter = ({ stringProcessed }) => async (character) => {
  const ms = calculateTimeout(character);
  await timeout(ms);
  return { stringProcessed: `${stringProcessed}${character}`};
};

const getResultFromState = ({ stringProcessed }) => {
  return stringProcessed;
}

const processString = async (string) => {
  // ?
  return result
}

Решение

Позвольте мне показать вам два подхода к этой проблеме: один чисто необходимый – что имеет смысл, но не хватает элегантности – и гораздо более краткое решение.

Первый подход: императивный способ

С помощью императивного подхода мы хотим описать каждый шаг функции, как это описывает в проблеме.

const processString = async (string) => {
  let state = initialState;
  const stringArray = stringToArray(string);
  for (let i = 0; i < stringArray.length; i++) {
    state = await processCharacter(state)(stringArray[i]);
  }
  return getResultFromState(result);
}

Быть справедливым, это работает, и это вернет вас именно то, что вы ищете. У меня все еще есть несколько проблем с этим кодом:

  • Мне нравится подход функционального программирования и повторное согласие Государство На каждом шаге меня каждый раз убивает немного больше,
  • старомодный для Петля чрезвычайно многословна, и я не думаю, что я использовал это, так как я узнал, как использовать карта и Foreach Анкет

“Но Марван, почему мы не можем использовать foreach ?” Вы можете спросить.

Давайте попробуем (и забудьте о моем первом пункте, NVM).

const processString = async (string) => {
  let state = initialState;
  stringToArray(string).forEach(async (character) => {
    state = await processCharacter(state)(stringArray[i]);
  });
  return getResultFromState(result);
}

Бриллиант! Но если вы запустите это в гисте для строки «Тест» Вы получаете … "" Анкет Чего ждать?

Конечно… Это не будет работать из-за определения Foreach : Вы можете в основном переосмыслить его, как показано ниже.

Array.prototype.forEach = function(fn) {
  for(let i = 0; i < this.length; i++) {
    fn(i);
  }
}

Вы видите, откуда исходит проблема? Да! От ждать ! На самом деле этот конкретный пункт дифференцирует foreach () и наш старый старый для петли. Если вы хотите сделать эту работу, вы, вероятно, захотите начать код этим:

Array.prototype.forEach = function(fn) {
  for(let i = 0; i < this.length; i++) {
    await fn(i);
  }
}

Но, пожалуйста, не так …

Вместо этого позвольте запустить вас через более элегантное решение.

Используя уменьшение ()

Для тех, кто прочитал несколько статей в моем блоге, вы, возможно, знаете о моем увлечении уменьшить () функция и Насколько мощно это может быть Анкет Вот один из примеров того, почему я так думаю.

Подумайте о такой проблеме: то, что мы хотим сделать, можно также быть написано ниже (давайте возьмем строку «Монстр» ).

processCharacter(initialState)('m')
  .then(state => processCharacter('o')(state))
  .then(state => processCharacter('n')(state))
  .then(state => processCharacter('s')(state))
  .then(state => processCharacter('t')(state))
  .then(state => processCharacter('e')(state))
  .then(state => processCharacter('r')(state));

Таким образом, итеративным рисунком по ряду элемента обычно отталкивает меня к уменьшить () Анкет

Давайте определим наш редуктор: мы в основном хотим наш аккумулятор и для каждого персонажа применить ProcessCharacter.

const reducer = (accumulator, character) => accumulator.then(state => processCharacter(character)(state));

Как наше первоначальное значение, мы все еще хотим иметь обещание быть в состоянии подать заявку .then к этому. Вот это: Обещание Это в основном обещание, которое разрешит наше начальное состояние и начнет заставлять наши домино падать.

Окончательное решение

// :: string -> [character]
const stringToArray = string => [...string];
const processString = async (string) => getResultFromState(
  stringToArray(string).reduce(
    (acc, character) => acc.then((state) => processCharacter(state)(character)),
    Promise.all(initialState),
  )
);

Я надеюсь, что вам понравилось прочитать эту статью!

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

Оригинал: “https://dev.to/marwaneb/tip-call-promises-synchronously-4j11”