Как трубы и монады работают вместе в JavaScript?

Я просмотрел похожие вопросы и ответы и не нашел ответа, который напрямую затрагивает мой вопрос. Я изо всех сил пытаюсь понять, как использовать Maybe или Either или Monads в сочетании с функциями трубопровода. Я хочу объединить функции вместе, но я хочу, чтобы труба остановилась и возвратила ошибку, если она возникает на любом этапе. Я пытаюсь реализовать концепции функционального программирования в приложении node.js, и это действительно мое первое серьезное исследование, так что ответ не будет таким простым, чтобы оскорбить мой интеллект по этому вопросу.

Я написал такую ​​функцию:

const _pipe = (f, g) => async (...args) => await g( await f(...args))

module.exports = {arguments.
    pipeAsync: async (...fns) => {
        return await fns.reduce(_pipe)
    }, 
...

Я называю это следующим образом:

    const token = await utils.pipeAsync(makeACall, parseAuthenticatedUser, syncUserWithCore, managejwt.maketoken)(x, y)  

Ответ 1

крючок, строка и грузило

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

Почему я говорю вам это? Хорошо, что JavaScript уже имеет превосходный API для асинхронных функций секвенирования с использованием встроенного, Promise.prototype.then

// never reinvent the wheel
const _pipe = (f, g) => async (...args) => await g( await f(...args))
myPromise.then (f) .then (g) .then (h) ...

Но вы хотите написать функциональные программы, не так ли? Это не проблема для функционального программиста. Изолируйте поведение, которое вы хотите абстрагировать (скрыть), и просто оберните его в параметризованную функцию - теперь, когда у вас есть функция, возобновите запись своей программы в функциональном стиле...

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

Ниже мы демонстрируем слева-направо состав асинхронных функций через comp. Для целей этой программы delay включается как создатель Promises, а sq и add1 - это примерные функции async.

const delay = (ms, x) =>
  new Promise (r => setTimeout (r, ms, x))

const sq = async x =>
  delay (1000, x * x)
  
const add1 = async x =>
  delay (1000, x + 1)

// just make a function  
const comp = (f,g) =>
  // abstract away the sickness
  x => f (x) .then (g)

// resume functional programming  
comp (sq, add1) (10)

  // this effect added for demo purposes
  .then (console.log, console.error)

  // 2 seconds later...
  // 101

Ответ 2

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

Короткий ответ заключается в том, что ваша функция _pipe распространяется только на ошибки. И останавливает выполнение функций, как только выдается ошибка.

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

Вот почему вы не можете этого сделать, потому что каждый раз он выдает ошибку:

const result = await pipeAsync(func1, func2)(a, b);

Чтобы использовать pipeAsync в текущем состоянии, вам понадобится два await s: один, чтобы получить результат pipeAsync и один, чтобы получить результат вызова этого результата:

const result = await (await pipeAsync(func1, func2))(a, b);

Решение

Удалите ненужные async и await из определения pipeAsync. Акт создания ряда функций, даже асинхронных функций, не является асинхронной операцией:

module.exports = {
    pipeAsync: (...fns) => fns.reduce(_pipe),

Как только вы это сделаете, все работает красиво:

const _pipe = (f, g) => async(...args) => await g(await f(...args))
const pipeAsync = (...fns) => fns.reduce(_pipe);

const makeACall = async(a, b) => a + b;
const parseAuthenticatedUser = async(x) => x * 2;
const syncUserWithCore = async(x) => {
  throw new Error("NOOOOOO!!!!");
};
const makeToken = async(x) => x - 3;

(async() => {
  const x = 9;
  const y = 7;

  try {
    // works up to parseAuthenticatedUser and completes successfully
    const token1 = await pipeAsync(
      makeACall,
      parseAuthenticatedUser
    )(x, y);
    console.log(token1);

    // throws at syncUserWithCore
    const token2 = await pipeAsync(
      makeACall,
      parseAuthenticatedUser,
      syncUserWithCore,
      makeToken
    )(x, y);
    console.log(token2);
  } catch (e) {
    console.error(e);
  }
})();