在 Javascript 中嵌套许多函数调用(Unix 管道)的好方法

Nice way to nest many function calls (Unix piping) in Javascript

我一直在寻找一种很好地进行嵌套函数调用的方法,以避免出现类似的情况:

var result = function1(function2(function3()));

或类似的东西:

var result = function3();
result = function2(result);
result = function1(result);

像 Unix 管道这样的东西会很好:

var result = function3() | function2() | function1();

来源:https://www.npmjs.com/package/babel-plugin-operator-overload

当然|是位或操作,这个例子是抽象的

有人知道有什么方法可以使用 ES5、ES6 或 ES7 实现这样的效果,而无需转译吗?

编辑

谢谢 T.J Crowder、torazaburo 和 Bergi,你们都在回答中添加了独特、有用和有趣的信息。

没有辅助函数

我最初接受你的问题是在 没有 任何辅助函数的情况下执行此操作,但你随后的评论表明情况并非如此。如果辅助函数在范围内,请跳过。

无需添加任何辅助函数,即可使用 ES6 promises:

Promise.resolve()
    .then(function3)
    .then(function2)
    .then(function1)
    .then(result => {
  console.log("result is " + result);
});

不比

漂亮
var result = function1(function2(function3()));

...但至少被调用的函数是按调用顺序列出的,而且 promises 在很多方面都非常灵活。

例如:Live copy on Babel's REPL

function function1(arg) {
  console.log("function1 called with " + arg);
  return "result1";
}
function function2(arg) {
  console.log("function2 called with " + arg);
  return "result2";
}
function function3() {
  console.log("function3 called");
  return "result3";
}

Promise.resolve()
    .then(function3)
    .then(function2)
    .then(function1)
    .then(result => {
  console.log("result is " + result);
});

输出:

function3 called
function2 called with result3
function1 called with result2
result is result1

有辅助功能

回复您的评论:

function pipe(){
    var str = 'Promise.resolve()';
    for(var i = 0; i < arguments.length; i++){
        str += '.then(arguments[' + i + '])'
    }
    eval(str);
}

 pipe(c, b, a, result => { console.log("result is " + result); });

I know pipe is a thing in fs libraries, so the function name isn't exactly great. Aside from that, is there anything glaringly wrong with this?

如果你想在这里抛出一个辅助函数,根本不需要 eval。对于非 promise 化的函数,只需执行:

function pipe(first, ...more) {
  return more.reduce((r, f) => f(r), first());
}

let result = pipe(function3, function2, function1);

Live copy on Babel's REPL

如果您想使用 promise 化函数或混合函数来执行此操作,则:

function pipe(...functions) {
  return functions.reduce((p, f) => p.then(f), Promise.resolve());
}

然后你就可以像你展示的那样称呼它了:

pipe(function3, function2, function1, result => {
    // ...
});

...但这样做会忽略错误。由于 pipe return 是最后一个承诺,您可以使用所有承诺的好处

pipe(function3, function2, function1, result => {
    // ...
}).catch(failure => {
    // ...
});

pipe(function3, function2, function1)
    .then(result => {
        // ...
    })
    .catch(failure => {
        // ...
    });

这是一个混合简单函数和 return 承诺的函数的完整示例:Live copy on Babel's REPL

function pipe(...functions) {
    return functions.reduce((p, f) => p.then(f), Promise.resolve());
}
function function1(arg) {
    console.log("function1 called with " + arg);
    return "result1";
}
function function2(arg) {
    console.log("function2 called with " + arg);
    return new Promise(resolve => {
        setTimeout(() => {
            resolve("result2");
        }, 100);
    });
}
function function3() {
    console.log("function3 called");
    return "result3";
}

pipe(function3, function2, function1)
    .then(result => {
        console.log("Final result is " + result);
    })
    .catch(failure => {
        console.log("Failed with " + failure);
    });

输出:

function3 called
function2 called with result3
function1 called with result2
Final result is result1

您只是在编写函数。使用许多库中提供的 compose 函数,或者编写您自己的函数,并将其用作:

compose(function1, function2, function3) ()

换句话说,您的 "pipe" 运算符可以被认为是一个 "comma",用于在调用 compose 时分隔参数。

这是一个真正简单的撰写:

function compose(...fns) {
  var lastFunc = fns.pop();
  return function() { 
    return fns.reduceRight(result, fn) {
      return fn(result);
    }, lastFunc(...arguments));
  };
}

你要找的是一个 pipe 函数,它基本上就是众所周知的 compose 翻转:

var result = pipe(function3, function2, function1)();

它不是内置的(也没有计划用于任何即将到来的 ES 修订版),但在许多库中可用,例如 Ramda;你可以自己简单地实现它:

function pipe(g, ...fs) {
    if (!arguments.length) return x => x;
    if (!fs.length) return g;
    const f = pipe(...fs);
    return x => f(g(x));
}

如果您正在寻找新的语法,an ES7 proposal that could bring some pipelining sugar to the language. It's not settled yet它到底是什么样子:

method3()::method2()::method1()
method3()->method2()->method1()
function3()->function2()->function1()