【问题标题】:Nice way to nest many function calls (Unix piping) in Javascript在 Javascript 中嵌套许多函数调用(Unix 管道)的好方法
【发布时间】:2016-02-09 01:54:49
【问题描述】:

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

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,你们在回答中添加了独特、有用和有趣的信息。

【问题讨论】:

  • 如果您自己编写函数,您可以让它们返回一个对象,并将所有其他函数作为方法。那会很慢,最后你需要.value() 或其他东西,但我想它对某些人来说更可爱。
  • 我正在自己编写函数,但我无法对用法做出任何假设。它们应该被划分,因此编写对每个函数的引用是不可行的。理想情况下,它应该适用于任何功能,但如果不可能,我愿意接受向该功能添加一些东西以使其成为可能的想法。但同样,这些函数不应该相互了解任何信息。
  • 我很好奇你现在将在什么环境下运行 ES7 代码...甚至 ES6,现在。
  • 这个问题与任何特定环境无关,但更多的是让我了解 Javascript 的发展方向。我昨天阅读了 ES6 和 ES7 计划和起草的特性,这是我的一个问题。它不一定与今天或明天有关。更何况我应该把时间花在哪里。为了直接回答这个问题,Node.js 已经原生实现了大多数 ES6 和一些 ES7,但我认为这是因为它在底层进行了转译。
  • @Jexah:不,NodeJS 使用 V8,它已经实现了一些 ES6 (ES2015) 甚至是 ES7 的一些提议功能。不转译。 V8 团队正在努力使 V8 完全符合最终确定的 ES2015 规范,但这项工作并非易事。

标签: javascript ecmascript-6 ecmascript-5 ecmascript-2016


【解决方案1】:

没有辅助函数

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

无需添加任何辅助函数,您就可以使用 ES6 Promise:

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);
});

输出:

调用函数3 用 result3 调用的 function2 使用结果 2 调用的函数 1 结果是结果1

带有辅助函数

你的评论:

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); });

我知道管道是 fs 库中的一个东西,所以函数名不是很好。除此之外,这还有什么明显的问题吗?

如果你想为此抛出一个辅助函数,则根本不需要eval。对于 non-promise-ified 函数,只需:

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-ified 函数或混合函数来执行此操作,那么:

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

那么你可以按照你展示的方式来称呼它:

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

...但是这样做会忽略错误。由于pipe返回最后一个promise,你可以使用所有promise good

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

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

这是一个混合简单函数和返回承诺的函数的完整示例: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);
    });

输出:

调用函数3 用 result3 调用的 function2 使用结果 2 调用的函数 1 最终结果是 result1

【讨论】:

  • @Jexah:任何时候你在一个字符串中构建代码以便eval 它,你可能走错路了。 :-) 我在上面的答案中添加了pipe 的简单实现。
  • 请不要将承诺用于同步函数。如果你想要.chain 语法,没有什么能阻止你使用它,但不要滥用承诺。
  • @Bergi:注意最后一个例子,混合了返回承诺和不返回承诺的函数。
  • @Bergi:我确实添加了pipe 的无承诺版本,但是,对于完全无承诺的情况。
  • 是的,我知道 eval 很糟糕,但我没有想到任何其他方法,这就是我要求批评它的原因。感谢您提供彻底的解决方案!
【解决方案2】:

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

compose(function1, function2, function3) ()

换句话说,您的“管道”运算符可以被认为是一个“逗号”,用于在 compose 调用中分隔参数。

这是一个真正简单的 compose:

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

【讨论】:

  • 大声笑,我同时添加了一个非承诺版本。 :-)
【解决方案3】:

您正在寻找的是一个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 可以为该语言带来一些流水线糖。是not settled yet 它到底是什么样子的:

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

【讨论】:

    猜你喜欢
    • 2017-09-18
    • 2012-02-07
    • 2019-09-19
    • 2017-04-05
    • 1970-01-01
    • 1970-01-01
    • 2015-03-18
    • 1970-01-01
    相关资源
    最近更新 更多