Javascript 我应该如何在小函数之间传递数据——通过闭包还是通过对象的属性?

Javascript 我应该如何在小函数之间传递数据——通过闭包还是通过对象的属性?,javascript,node.js,oop,design-patterns,functional-programming,Javascript,Node.js,Oop,Design Patterns,Functional Programming,我有一个复杂的业务操作,例如删除用户帐户。它包含多个连接的步骤,并且必须跟踪步骤之间的某些状态。编写此操作的更好方法是什么 我看到了很多类似下面的功能性方法 function someAction(someParam, anotherParam, callback) { async.waterfall([ step1, step2, step3, step4 ],ca

我有一个复杂的业务操作,例如删除用户帐户。它包含多个连接的步骤,并且必须跟踪步骤之间的某些状态。编写此
操作
的更好方法是什么

我看到了很多类似下面的
功能性
方法

function someAction(someParam, anotherParam, callback) {

    async.waterfall([
            step1,
            step2,
            step3,
            step4
            ],callback
    );

    function step1(p,cb){/**use someParam and anotherParam here via closure*/}
    function step2(p,cb){/**...*/}
    function step3(p,cb){/**...*/}
    function step4(p,cb){/**...*/}
};

someAction('value', 1241, (err)=>{/**...*/});
我不喜欢这种方法的地方在于,所有内容都是在单个函数的范围内定义的(这里是
someAction

我找到了一种更具可读性的
面向对象的方法。状态和
stepX
函数不是真正私有的-有时它便于测试

function SomeAction(someParam, anotherParam){
    //private state
    this._someParam = someParam;
    this._anotherParam = anotherParam;
};

SomeAction.prototype._step1 = function(p, cb){
    //use this._someParam and this._anotherParam
};

SomeAction.prototype._step2 = function(p, cb){
    //use this._someParam and this._anotherParam
};

SomeAction.prototype._step3 = function(p, cb){
    //use this._someParam and this._anotherParam
};

SomeAction.prototype._step4 = function(p, cb){
    //use this._someParam and this._anotherParam
};

//public api
SomeAction.prototype.execute = function(callback) {
    async.waterfall([
                this._step1,
                this._step2,
                this._step3,
                this._step4
            ],callback
    )
};

new SomeAction('value', 1241).execute((err)=>{/**...*/})

它们之间有什么性能差异吗?Node.js中推荐的方法是什么?每次我在函数方法中调用
someAction
,所有
stepX
函数都必须从头定义,这是真的吗

您可以创建步骤函数的咖喱版本,并将它们粘贴到瀑布中

function step1(arg1, arg2, cb){
  // Fuction body...
}

// other steps would be defined here...

function step4(arg1, cb){
  // fuction body...
}

curried_step1 = step1.bind(undefined, 'junk', 'garb');
// Other steps curried here...
curried_step4 = step4.bind(undefined, 'something');

async.waterfall([
            curried_step1,
            curried_step2,
            curried_step3,
            curried_step4
        ],callback
);

另一种方法是将数据和状态包装到一个对象中(代替真正的monad),并使用该对象传递您需要的内容。

您可以创建step函数的通用版本,并将它们粘贴到瀑布中

function step1(arg1, arg2, cb){
  // Fuction body...
}

// other steps would be defined here...

function step4(arg1, cb){
  // fuction body...
}

curried_step1 = step1.bind(undefined, 'junk', 'garb');
// Other steps curried here...
curried_step4 = step4.bind(undefined, 'something');

async.waterfall([
            curried_step1,
            curried_step2,
            curried_step3,
            curried_step4
        ],callback
);

另一种方法是将数据和状态包装成一个对象(代替真正的monad),并使用该对象传递您需要的信息。

您可以使用Promissions,其样式如下所示:

var Promise = require('promise');

//** Execute Program **//
main();

/**
 * Do your async logic flow here
 */
function main() {
  step1()
    .then(step2) //Wait for step1 to finish, and pass the response directly to step2
    .then(function(res) {
      // Do something with res (the return from step2)
      // Async execute step3 & step4
      var promises = [
        step3(),
        step4()
      ];

      // Wait for step3 & step4 to finish:
      Promise.all([promises[0], promises[1]]).then(function(values) {
        console.log(values[0]); //Value from step3
        console.log(values[1]); //Value from step4
      }).catch(function(e) {
        console.log(e); //Reject or thrown errors from steps3 or 4
      });
    }).catch(function(e) {
      console.log(e); //Reject or thrown errors from steps1 or 2
    });
}

function step1() {
  return new Promise(resolve, reject) {
    //some async task here
    resolve('step1'); 
    //reject('step1'); //Or trigger a .catch (i.e. this function failed)
  });
}

function step2() {
  return new Promise(resolve, reject) {
    //some async task here
    resolve('step2'); 
    //reject('step2'); //Or trigger a .catch (i.e. this function failed)
  });
}

function step3() {
  return new Promise(resolve, reject) {
    //some async task here
    resolve('step3'); 
    //reject('step3'); //Or trigger a .catch (i.e. this function failed)
  });
}

function step4() {
  return new Promise(resolve, reject) {
    //some async task here
    resolve('step4'); 
    //reject('step4'); //Or trigger a .catch (i.e. this function failed)
  });
}

您可以使用Promissions(承诺)来创建如下样式:

var Promise = require('promise');

//** Execute Program **//
main();

/**
 * Do your async logic flow here
 */
function main() {
  step1()
    .then(step2) //Wait for step1 to finish, and pass the response directly to step2
    .then(function(res) {
      // Do something with res (the return from step2)
      // Async execute step3 & step4
      var promises = [
        step3(),
        step4()
      ];

      // Wait for step3 & step4 to finish:
      Promise.all([promises[0], promises[1]]).then(function(values) {
        console.log(values[0]); //Value from step3
        console.log(values[1]); //Value from step4
      }).catch(function(e) {
        console.log(e); //Reject or thrown errors from steps3 or 4
      });
    }).catch(function(e) {
      console.log(e); //Reject or thrown errors from steps1 or 2
    });
}

function step1() {
  return new Promise(resolve, reject) {
    //some async task here
    resolve('step1'); 
    //reject('step1'); //Or trigger a .catch (i.e. this function failed)
  });
}

function step2() {
  return new Promise(resolve, reject) {
    //some async task here
    resolve('step2'); 
    //reject('step2'); //Or trigger a .catch (i.e. this function failed)
  });
}

function step3() {
  return new Promise(resolve, reject) {
    //some async task here
    resolve('step3'); 
    //reject('step3'); //Or trigger a .catch (i.e. this function failed)
  });
}

function step4() {
  return new Promise(resolve, reject) {
    //some async task here
    resolve('step4'); 
    //reject('step4'); //Or trigger a .catch (i.e. this function failed)
  });
}

这不完全是一个答案,但回答了您的间接问题:

问题是我应该如何在这些小函数之间传递数据——通过闭包或通过对象的属性

还有第三条路。如果您熟悉OO设计,那么您可能熟悉命令模式的概念。也就是说,您需要动态构造一个函数,但这是不可能的,因此您可以使用一种方法创建一个对象,然后根据对象的属性进行自定义

在函数式编程中,此设计模式相当于函数工厂模式。基本上,您编写一个函数来生成另一个函数

因此,您希望将
someParam
anotherParam
传递给异步函数,但希望能够在
someAction
函数之外编写该函数。以下是您如何做到这一点:

function someAction (someParam, anotherParam, callback) {

    async.waterfall([
        make_step1(someParam,anotherParam),
        make_step2(someParam,anotherParam)
        /* ... */
        ],callback
    );
}

function make_step1 (someParam, anotherParam) {
    return function (p, cb) {
        // use someParam and anotherParam here
    }
}

function make_step2 (someParam, anotherParam) {
    return function (p, cb) {
        // use someParam and anotherParam here
    }
}

// ...
这消除了您对函数代码提出的主要异议:您不再需要在
someAction()
中定义所有步骤函数,这使得它看起来更类似于OO代码


每次调用
someAction()
时,仍然会创建所有步骤函数的新实例(现在仅从make函数返回)。但是解释器不必再次编译函数。相反,只创建一个新的闭包(将闭包视为与程序堆栈解除链接的冻结堆栈帧)。

这不是一个完全正确的答案,而是回答了您的间接问题:

问题是我应该如何在这些小函数之间传递数据——通过闭包或通过对象的属性

还有第三条路。如果您熟悉OO设计,那么您可能熟悉命令模式的概念。也就是说,您需要动态构造一个函数,但这是不可能的,因此您可以使用一种方法创建一个对象,然后根据对象的属性进行自定义

在函数式编程中,此设计模式相当于函数工厂模式。基本上,您编写一个函数来生成另一个函数

因此,您希望将
someParam
anotherParam
传递给异步函数,但希望能够在
someAction
函数之外编写该函数。以下是您如何做到这一点:

function someAction (someParam, anotherParam, callback) {

    async.waterfall([
        make_step1(someParam,anotherParam),
        make_step2(someParam,anotherParam)
        /* ... */
        ],callback
    );
}

function make_step1 (someParam, anotherParam) {
    return function (p, cb) {
        // use someParam and anotherParam here
    }
}

function make_step2 (someParam, anotherParam) {
    return function (p, cb) {
        // use someParam and anotherParam here
    }
}

// ...
这消除了您对函数代码提出的主要异议:您不再需要在
someAction()
中定义所有步骤函数,这使得它看起来更类似于OO代码


每次调用
someAction()
时,仍然会创建所有步骤函数的新实例(现在仅从make函数返回)。但是解释器不必再次编译函数。相反,只创建一个新的闭包(将闭包视为与程序堆栈断开链接的冻结堆栈框架)。

建议的方法是更易于阅读且符合您的要求的方法(可测试性)。性能差异可以忽略不计。最好的解决方案是使用承诺。它们既是对象又是功能性的。功能性并不意味着将整个逻辑放在一个功能中。这意味着设计每个具有单一用途的小功能,然后组合这些功能以形成更复杂的功能。使用功能范例,您不会获得封装,而是代码重用,并且由于没有副作用而提高了可维护性。@LUH3417感谢您的解释!我介绍的两种方法都由许多小函数组成(我喜欢这种风格——由Bob叔叔推荐)。问题是我应该如何在这些小函数之间传递数据——通过闭包或通过对象的属性。bob叔叔建议类是一个用来存放数据和操作数据的方法的袋子。现在我明白了。我很困惑,因为您在包装器中定义了
stepX
函数(当然是为了关闭它们),因此无法再重用它们。您可以将不断变化的状态作为附加参数传递,并使用状态单子将其抽象掉。如果共享状态没有改变,读卡器monad是另一种选择。另一个问题是您是否使用不可变数据。归根结底,我认为这个问题太宽泛了。推荐的方法是