Как трубы и монады работают вместе в 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)
2 ответа
крюк, леска и грузило
Я не могу не подчеркнуть, насколько важно, чтобы вы не цеплялись за все новые термины, которые, как вам кажется, нужно выучить - функциональное программирование связано с функциями - и, возможно, единственное, что вам нужно понять о функции, это то, что она позволяет вам абстрагировать часть вашей программы с помощью параметра; или несколько параметров, если необходимо (это не так) и поддерживается вашим языком (обычно это так)
Почему я говорю тебе это? Ну, 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
включен как создатель Обещаний, и sq
а также add1
примерные асинхронные функции
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
придумать собственное удобство
Вы можете сделать вариа compose
который принимает любое количество функций - также обратите внимание, как это позволяет вам смешивать функции синхронизации и асинхронности в одной композиции - преимущество подключения прямо в .then
, который автоматически продвигает не обещание возврата значений в обещание
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)
// make all sorts of functions
const effect = f => x =>
(f (x), x)
// invent your own convenience
const log =
effect (console.log)
const comp = (f,g) =>
x => f (x) .then (g)
const compose = (...fs) =>
fs.reduce (comp, x => Promise.resolve (x))
// your ritual is complete
compose (log, add1, log, sq, log, add1, log, sq) (10)
// effect added for demo
.then (console.log, console.error)
// 10
// 1 second later ...
// 11
// 1 second later ...
// 121
// 1 second later ...
// 122
// 1 second later ...
// 14884
Работай умом, а не силой
comp
а также compose
это простые в освоении функции, которые практически не требовали написания. Потому что мы использовали встроенный .then
все средства обработки ошибок подключаются к нам автоматически. Вам не нужно беспокоиться о вручную await
или try/catch
или же .catch
'- еще одно преимущество написания наших функций таким образом
нет ничего постыдного в абстракции
Это не значит, что каждый раз, когда вы пишете абстракцию, это делается для того, чтобы скрыть что-то плохое, но это может быть очень полезно для множества задач - например, "скрытие" императивного стиля. while
,
const append = (xs, x) =>
xs.concat ([x])
const fibseq = n => {
let seq = []
let a = 0
let b = 1
while (n >= 0) {
n = n - 1
seq = append (seq, a)
a = a + b
b = a - b
}
return seq
}
console.log (fibseq (500))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ... ]
Но вы хотите писать функциональные программы, верно? Это не проблема для функционального программиста. Мы можем создать собственный механизм зацикливания, но на этот раз он будет использовать функции и выражения вместо операторов и побочных эффектов - и все это без ущерба для скорости, читаемости или безопасности стека.
Вот, loop
постоянно применяет функцию с помощью нашего recur
контейнер значений. Когда функция возвращает не recur
значение, вычисление завершено, и возвращается окончательное значение.
const recur = (...values) =>
({ type: recur, values })
// break the rules sometimes; reinvent a better wheel
const loop = f =>
{
let acc = f ()
while (acc && acc.type === recur)
acc = f (...acc.values)
return acc
}
const fibseq = x =>
loop ((n = x, seq = [], a = 0, b = 1) =>
n === 0
? seq.concat ([a])
: recur (n - 1, seq.concat ([a]), a + b, a))
console.time ('loop/recur')
console.log (fibseq (500))
console.timeEnd ('loop/recur')
// [ 0,
// 1,
// 1,
// 2,
// 3,
// 5,
// 8,
// 13,
// 21,
// 34,
// ... 490 more items ]
// loop/recur: 5ms
ничто не является священным
И помните, вы можете делать все, что хотите. В этом нет ничего волшебного then
- кто-то где-то решил это сделать. Вы можете быть кем-то в каком-то месте и просто сделать свой собственный then
- Вот then
это своего рода функция прямой композиции - так же, как Promise.prototype.then
, это автоматически применяется then
не- then
возвращаемые значения; мы добавляем это не потому, что это особенно хорошая идея, а чтобы показать, что мы можем вести себя подобным образом, если бы захотели
Когда понимаешь then
, вы поймете мать всех монад - не забудьте сосредоточиться на механике, а не на условиях
const then = x =>
x && x.type === then
? x
: Object.assign (f => then (f (x)), { type: then })
const sq = x =>
then (x * x)
const add1 = x =>
x + 1
const effect = f => x =>
(f (x), x)
const log =
effect (console.log)
then (10) (log) (sq) (log) (add1) (add1) (add1) (log)
// 10
// 100
// 101
sq (2) (sq) (sq) (sq) (log)
// 65536
что это за язык?
Это даже больше не похоже на JavaScript - кого это волнует? Это ваша программа, и вы решаете, как она будет выглядеть. Хороший язык не встанет у вас на пути и не заставит вас писать свою программу в каком-либо конкретном стиле; функциональный или иной
Это на самом деле JavaScript, просто не поддавшийся ошибочным представлениям о том, что он способен выразить
then (10) (log) (sq) (log) (add1) (add1) (add1) (log)
// 10
// 100
// 103
// => { Then: 103 }
sq (5) (log) (add1) (log)
// 25
// 26
// => { Then: 26 }
отправим его
Мы просто использовали имена comp
а также compose
в наших локальных фрагментах, но когда вы упаковываете свою программу, вы должны выбрать имена, которые имеют смысл с учетом вашего конкретного контекста - см. комментарий Берги для рекомендации
Ответ Наомик очень интересен, но, похоже, она не удосужилась ответить на ваш вопрос.
Короткий ответ: ваш _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);
}
})();
Это также может быть написано без использования async
совсем:
const _pipe = (f, g) => (...args) => Promise.resolve().then(() => f(...args)).then(g);
const pipeAsync = (...fns) => fns.reduce(_pipe);
const makeACall = (a, b) => Promise.resolve(a + b);
const parseAuthenticatedUser = (x) => Promise.resolve(x * 2);
const syncUserWithCore = (x) => {
throw new Error("NOOOOOO!!!!");
};
const makeToken = (x) => Promise.resolve(x - 3);
const x = 9;
const y = 7;
// works up to parseAuthenticatedUser and completes successfully
pipeAsync(
makeACall,
parseAuthenticatedUser
)(x, y).then(r => console.log(r), e => console.error(e));
// throws at syncUserWithCore
pipeAsync(
makeACall,
parseAuthenticatedUser,
syncUserWithCore,
makeToken
)(x, y).then(r => console.log(r), e => console.error(e))