Nodejs 用Node.js編寫複雜控制流的解決之道:async庫+大量使用高階函數和柯里化。

Nodejs 用Node.js編寫複雜控制流的解決之道:async庫+大量使用高階函數和柯里化。

async是一個非常好用的模塊,祗是如果沒有高階函數的使用,用起來代碼還是相當混亂。下面是我寫的一個簡單的柯里化(部分求值)函數:

Function.prototype.curry = function () {
  var appliedArgs = arguments;
  var self = this;
  return function () {
    var j = 0;
    var args = [];
    for (var i = 0; i < appliedArgs.length; i++) {
      if (appliedArgs[i] === undefined) {
        appliedArgs[i] = arguments[j];
        j ++;
      }
      args.push(appliedArgs[i]);
    }
    for (; j < arguments.length; j++) {
      args.push(arguments[j]);
    }
    return self.apply(this, args);
  };
}

例如函數:

function func(a, b, c, d, e) {
 //do something
}

var f1 = func.curry('a', 'b', 'c');
f1('d', 'e')

var f2 = func.curry(undefined, 'b', 'c', undefined, 'e');
f2('a', 'd')

以後可以把控制流所有的部分抽象爲高階函數,然後通過柯里化結合async的parallel, series, waterfall等方法,即可寫出清晰可讀的複雜控制流。


9 回复

Node.js 用Node.js編寫複雜控制流的解決之道:async庫+大量使用高階函數和柯里化

在Node.js中,處理複雜的異步控制流可能會變得非常困難。這時,async庫可以幫助我們更好地管理這些流程,而高階函數和柯里化則能讓我們的代碼更加模組化和易於理解。

async庫的基本使用

async庫提供了幾個重要的函數來管理異步操作,包括:

  • async.parallel: 平行執行多個異步操作。
  • async.series: 按順序串行執行多個異步操作。
  • async.waterfall: 按順序串行執行多個異步操作,並把上一個操作的結果傳給下一個操作。
const async = require('async');

// 示例:使用async.parallel
async.parallel([
  function(callback) {
    setTimeout(() => {
      console.log('Task 1 completed');
      callback(null, 'Result 1');
    }, 1000);
  },
  function(callback) {
    setTimeout(() => {
      console.log('Task 2 completed');
      callback(null, 'Result 2');
    }, 500);
  }
], function(err, results) {
  console.log(results); // ['Result 1', 'Result 2']
});

高階函數和柯里化的應用

接下來,我們看看如何利用高階函數和柯里化來進一步優化控制流。

首先,我們定義一個柯里化的函數:

Function.prototype.curry = function() {
  const appliedArgs = arguments;
  const self = this;
  return function() {
    let j = 0;
    const args = [];
    for (let i = 0; i < appliedArgs.length; i++) {
      if (appliedArgs[i] === undefined) {
        appliedArgs[i] = arguments[j];
        j++;
      }
      args.push(appliedArgs[i]);
    }
    for (; j < arguments.length; j++) {
      args.push(arguments[j]);
    }
    return self.apply(this, args);
  };
};

接下來,我們定義一個具體的函數 func 並進行柯里化:

function func(a, b, c, d, e) {
  console.log(`Called with: ${a}, ${b}, ${c}, ${d}, ${e}`);
}

const f1 = func.curry('a', 'b', 'c');
f1('d', 'e'); // Called with: a, b, c, d, e

const f2 = func.curry(undefined, 'b', 'c', undefined, 'e');
f2('a', 'd'); // Called with: a, b, c, d, e

結合async和柯里化

現在,我們可以將這些概念結合起來,以一種更模組化的方式來處理異步操作。

async.series([
  function(callback) {
    const result = f1('d', 'e');
    callback(null, result);
  },
  function(callback) {
    const result = f2('a', 'd');
    callback(null, result);
  }
], function(err, results) {
  console.log(results); // ['Called with: a, b, c, d, e', 'Called with: a, b, c, d, e']
});

這樣,我們就可以通過高階函數和柯里化來構建更清晰、更易於維護的異步控制流。


啊?难道你不知道Jscex吗?要清晰可读怎么会比得过Jscex……不信你说一段逻辑,我们来比一下,哈哈。

當然知道jscex啊,也用過,但就是不喜歡加一層編譯。

太了解我了!

啊?难道你不知道 Moescript 吗?要青紫刻度怎么会比得过 Moescript……不信你说一段逻辑,我们来比一下,哈哈。

(跟风莫喷)

楼主的思路不错。

function incrAB(callback) {
  async.parallel([
    function(_callback) {
      redis.incr('a', _callback)
    }),
    function(_callback) {
      redis.incr('b', _callback)
    })
  ], callback);
};
function incrAB(callback) {
  async.parallel([
    redis.incr.curry(redis, 'a'),
    redis.incr.curry(redis, 'b')
  ], callback)
};



+1 就是這種方法。此外還可以增加其他高階函數的方法,譬如修改回調函數的參數個數,傳遞多餘的參數等等

给出的下面的例子中 incrAB 这个方法,为什么不用定义_callback 这个参数了呢?我觉得每次传_callback也挺麻烦的,你是怎么省略的?

在Node.js中处理复杂的控制流时,结合async库、高阶函数和柯里化可以极大地提高代码的可读性和可维护性。这里展示如何通过这些技术来简化异步操作的控制流。

示例:使用async库和柯里化

假设我们需要执行一系列异步操作,例如读取文件、解析数据,并将结果发送到API。我们可以使用async库中的series方法来确保操作按顺序执行,并使用柯里化来减少重复代码。

安装依赖

首先,确保已经安装了async库:

npm install async

实例代码

const fs = require('fs').promises;
const async = require('async');

// 高阶函数:读取文件并解析为JSON
function readFileAsync(filename) {
  return async.apply(fs.readFile, filename, 'utf-8').curry();
}

// 高阶函数:将数据发送到API
function sendDataToApi(data) {
  return async.apply(sendData, data);
}

// 柯里化后的异步函数
const readAndParseFile = readFileAsync.curry('data.json').curry();
const sendParsedData = sendDataToApi.curry();

// 主逻辑
async.series([
  readAndParseFile.then(JSON.parse),
  sendParsedData
], (err, results) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  console.log('Success:', results);
});

// 假设的sendData函数
function sendData(data) {
  console.log('Sending data:', data);
}

在这个例子中:

  1. readFileAsync 是一个高阶函数,它接受一个文件名并返回一个读取该文件的异步函数。
  2. sendDataToApi 是另一个高阶函数,它接受数据并返回一个发送数据的异步函数。
  3. readAndParseFilesendParsedData 是通过柯里化生成的具体异步操作。
  4. 使用 async.series 来保证操作顺序执行。

这样,即使有多个步骤或复杂的控制流,我们也能通过组合高阶函数和柯里化来保持代码的简洁和可维护性。

回到顶部