Skip to content

Currying in JavaScript – Vom Funktions-Chaos zur klaren Modularität

Published: at 07:00 AMSuggest Changes

Die funktionale Programmierung ist längst kein Nischenthema mehr. Gerade in modernen JavaScript-Anwendungen gewinnen funktionale Prinzipien an Bedeutung. Ein besonders spannendes Konzept, das auf den ersten Blick ungewohnt, auf den zweiten aber extrem nützlich wirkt, ist Currying.

Currying ist eine Technik, mit der Funktionen, die mehrere Argumente erwarten, in eine Abfolge von Funktionen umgewandelt werden, die jeweils nur ein einziges Argument akzeptieren. Klingt theoretisch? Keine Sorge. In diesem Artikel erklären wir Dir das Konzept verständlich, zeigen Dir die Vor- und Nachteile und liefern praxisnahe Codebeispiele für Deinen JavaScript-Alltag.


Was ist Currying?

Unter Currying versteht man den Prozess, bei dem eine Funktion mit mehreren Parametern in eine Reihe von einzelnen Funktionen transformiert wird, die jeweils einen Parameter erwarten. Jede dieser Funktionen gibt eine neue Funktion zurück, bis schließlich alle Argumente gesammelt wurden und der ursprüngliche Funktionskörper ausgeführt werden kann.

Ein Beispiel macht das Prinzip deutlich:

// Ungecurried
function multiply(a, b) {
  return a * b;
}

// Gecurryte Variante
function curriedMultiply(a) {
  return function(b) {
    return a * b;
  };
}

const double = curriedMultiply(2);
console.log(double(5)); // Ausgabe: 10

Der Aufruf curriedMultiply(2) erzeugt eine neue Funktion, die auf das zweite Argument wartet – in diesem Fall b. Erst wenn beide Argumente übergeben wurden, erfolgt die Multiplikation. Dieses Verhalten ist nicht nur spannend, sondern auch äußerst hilfreich, wenn es um Wiederverwendbarkeit und Komposition geht.


Warum sollte man Currying nutzen?

Ein klarer Vorteil von Currying ist die Teil-Anwendung von Funktionen. Wenn Du eine Funktion mit mehreren Parametern hast, kannst Du sie durch Currying teilweise anwenden und später vervollständigen. Dadurch entstehen hochgradig modulare und testbare Codeeinheiten.

Gerade im UI-Development lassen sich viele Szenarien elegant mit gecurryten Funktionen lösen:

function setStyle(property) {
  return function(value) {
    return function(element) {
      element.style[property] = value;
    };
  };
}

const setBackground = setStyle('backgroundColor');
const setRedBackground = setBackground('red');

const myDiv = document.querySelector('#myDiv');
setRedBackground(myDiv);

Statt jedes Mal die gleiche style-Zuweisung zu schreiben, können Entwickler:innen durch Currying kleine, wiederverwendbare Funktionsbausteine schaffen.


Currying vs. Partial Application

Currying wird oft mit Partial Application verwechselt. Obwohl die beiden Konzepte unterschiedlich sind. Beim Currying wird die Funktion immer mit einem Argument nach dem anderen aufgerufen. Bei Partial Application hingegen wird eine Funktion mit mehreren (aber nicht allen) Argumenten vorausgefüllt.

Beispiel für Partial Application:

function sum(a, b, c) {
  return a + b + c;
}

function partial(fn, a) {
  return function(b, c) {
    return fn(a, b, c);
  };
}

const addFive = partial(sum, 5);
console.log(addFive(10, 15)); // → 30

Beim Currying hingegen sähe der Aufruf so aus:

function currySum(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}

console.log(currySum(5)(10)(15)); // → 30

Beide Techniken lassen sich kombinieren, unterscheiden sich aber im Aufrufmuster.


Currying manuell und automatisch

Die manuelle Umsetzung von Currying – wie in den bisherigen Beispielen – ist zwar lehrreich, aber nicht besonders skalierbar. Für größere Funktionen lässt sich Currying auch automatisieren. Eine häufig genutzte Hilfsfunktion sieht so aus:

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...nextArgs) {
        return curried.apply(this, args.concat(nextArgs));
      };
    }
  };
}

Das aktive Codebeispiel zeigt die Implementierung einer Funktion namens curry, die eine beliebige Funktion in eine “curried” Version umwandelt.

Die Funktion curry nimmt eine Funktion fn als Argument entgegen und gibt eine neue Funktion namens curried zurück. Diese curried-Funktion ist dafür verantwortlich, die Argumente schrittweise zu sammeln, bis genügend Argumente vorhanden sind, um die ursprüngliche Funktion fn auszuführen.

Die curried-Funktion akzeptiert beliebig viele Argumente (…args). Sie prüft, ob die Anzahl der übergebenen Argumente (args.length) größer oder gleich der Anzahl der erwarteten Argumente der ursprünglichen Funktion fn ist (fn.length).

Wenn genügend Argumente vorhanden sind: Die ursprüngliche Funktion fn wird mit den gesammelten Argumenten ausgeführt, indem fn.apply(this, args) aufgerufen wird.

Wenn nicht genügend Argumente vorhanden sind: Eine neue Funktion wird zurückgegeben, die weitere Argumente (…nextArgs) akzeptiert. Diese neuen Argumente werden mit den bereits gesammelten Argumenten (args) kombiniert, und die curried-Funktion wird erneut aufgerufen.

Wenn nicht genügend Argumente übergeben werden, ruft die curried-Funktion sich selbst rekursiv auf, bis alle benötigten Argumente gesammelt wurden. Sobald genügend Argumente vorhanden sind, wird die ursprüngliche Funktion fn ausgeführt.

Verwendung:

function multiply(a, b, c) {
  return a * b * c;
}

const curriedMultiply = curry(multiply);

// mögliche Varianten
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
console.log(curriedMultiply(2, 3, 4)); // 24

In diesem Beispiel wird die Funktion multiply in eine curried Version umgewandelt. Sie kann nun entweder alle Argumente auf einmal oder schrittweise entgegennehmen.

Die Vorteile eines solchen Patterns ist die daraus resultierende Flexibilität. Currying ermöglicht es, Funktionen schrittweise aufzurufen, was besonders in funktionalen Programmierparadigmen nützlich ist.

Ein weiterer Vorteil ist die Wiederverwendbarkeit. Currying erlaubt es, Funktionen teilweise zu “fixieren”, indem einige Argumente vorab übergeben werden, während andere später ergänzt werden.

Dieses Beispiel zeigt, wie Currying in JavaScript implementiert werden kann, um Funktionen flexibler und modularer zu gestalten. So lassen sich beliebige Funktionen in ihre gecurryte Form überführen, ohne, dass man sie manuell neu schreiben muss.


Nachteile und Herausforderungen

Currying ist mächtig, aber nicht immer sinnvoll. Vor allem in JavaScript, das nicht primär als funktionale Sprache konzipiert ist, kann exzessives Currying zu verwirrendem Code führen. Das gilt insbesondere für juniorige Entwickler:innen, die nicht mit dem Paradigma vertraut sind.

Ein weiterer Nachteil ist, dass gecurryte Funktionen oft anonyme Zwischenfunktionen erzeugen, die schwer zu debuggen oder zu testen sind. Auch die Lesbarkeit kann leiden, wenn mehrere gecurryte Funktionen verschachtelt verwendet werden:

const result = doSomething(a)(b)(c)(d);

Solcher Code kann schnell in sogenannte „Function Hell“ führen. Das gilt besonders, wenn kein klares Benennungskonzept vorhanden ist.


Fazit: Currying als Werkzeug mit Stil einsetzen

Currying ist ein elegantes Werkzeug aus der funktionalen Programmierung, das auch in JavaScript seinen Platz hat. Richtig eingesetzt, sorgt es für saubere, modulare und wiederverwendbare Funktionen. Besonders hilfreich ist Currying in Kombination mit anderen funktionalen Konzepten wie Function Composition oder Higher-Order Functions.

Allerdings sollte Currying bewusst und gezielt verwendet werden. Nicht als Selbstzweck, sondern dort, wo es die Klarheit und Struktur des Codes verbessert.


FAQ: Currying in JavaScript – 10 häufige Fragen ausführlich beantwortet

Was ist Currying in JavaScript genau?

Currying ist der Prozess, bei dem eine Funktion, die mehrere Argumente benötigt, in eine Sequenz von Funktionen umgewandelt wird, die jeweils nur ein Argument akzeptieren. Das Konzept stammt aus der funktionalen Programmierung und hilft dabei, Funktionen klarer zu strukturieren und flexibler wiederzuverwenden.

Beispiel:

function multiply(a, b) {
  return a * b;
}

// Curry-Variante
function curriedMultiply(a) {
  return function(b) {
    return a * b;
  };
}

const double = curriedMultiply(2);
console.log(double(10)); // → 20

Warum ist Currying nützlich?

Der große Vorteil von Currying liegt in der Teil-Anwendung (Partial Application). So kannst Du eine Funktion teilweise konfigurieren und später vervollständigen. Dadurch werden Funktionen wiederverwendbarer und modularer, was besonders bei komplexeren Projekten oder UI-Interaktionen nützlich ist.

const greet = salutation => name => `${salutation}, ${name}!`;

const sayHello = greet("Hallo");
console.log(sayHello("Anna")); // → Hallo, Anna!

Was ist der Unterschied zwischen Currying und Partial Application?

Beide Konzepte zielen auf Wiederverwendbarkeit ab, aber sie funktionieren unterschiedlich:

Beispiel Currying:

const add = a => b => c => a + b + c;
console.log(add(1)(2)(3)); // → 6

Beispiel Partial Application:

function add(a, b, c) {
  return a + b + c;
}

function partiallyApply(fn, ...presetArgs) {
  return function(...laterArgs) {
    return fn(...presetArgs, ...laterArgs);
  };
}

const addOneAndTwo = partiallyApply(add, 1, 2);
console.log(addOneAndTwo(3)); // → 6

Gibt es Libraries, die Currying unterstützen?

Ja, Bibliotheken wie Lodash oder Ramda bieten eingebaute Currying-Funktionen.

Mit Lodash:

import curry from 'lodash/curry';

function sum(a, b, c) {
  return a + b + c;
}

const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // → 6

Diese Libraries sind besonders dann hilfreich, wenn Du mit komplexeren Funktionen arbeitest, ohne die ganze Currying-Logik selbst schreiben zu müssen.

Wie curry ich eine Funktion mit mehreren Argumenten in Vanilla JavaScript?

Du kannst Dir eine eigene curry-Hilfsfunktion schreiben:

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...nextArgs) {
        return curried(...args.concat(nextArgs));
      };
    }
  };
}

function volume(l, w, h) {
  return l * w * h;
}

const curriedVolume = curry(volume);
console.log(curriedVolume(2)(3)(4)); // → 24

Diese Technik funktioniert mit beliebig vielen Argumenten.

Gibt es Probleme mit this bei gecurryten Funktionen?

Ja, Currying kann problematisch werden, wenn Deine Funktion auf this zugreift, wie es bei Klassenmethoden üblich ist. Das liegt daran, dass this seinen Kontext verlieren kann, wenn die Methode als Funktion weitergereicht wird.

Beispielproblem:

class Button {
  constructor(label) {
    this.label = label;
  }

  handleClick(message) {
    console.log(`${this.label}: ${message}`);
  }
}

const button = new Button('Senden');
const curriedClick = msg => button.handleClick(msg);
curriedClick('geklickt!'); // Funktioniert

// Aber Vorsicht mit `this` in Curry-Form innerhalb von Klassenmethoden

Verwende in solchen Fällen entweder .bind(this) oder Arrow Functions, die this aus dem äußeren Kontext übernehmen.

Wie debugge ich gecurryte Funktionen besser?

Anonyme, verschachtelte Funktionen machen das Debuggen schwer. Deshalb ist es hilfreich, Zwischenschritte zu benennen oder mit console.log zu arbeiten.

Statt:

const sum = a => b => c => a + b + c;

Besser:

function addA(a) {
  return function addB(b) {
    return function addC(c) {
      console.log(a, b, c);
      return a + b + c;
    };
  };
}

console.log(addA(1)(2)(3)); // Logs: 1 2 3 → 6

Oder verwende eine IDE mit „step-through“-Debugging wie VSCode.

Kann Currying auch bei Event Handlern hilfreich sein?

Definitiv. Besonders, wenn Du Event Handler parametrisieren willst, ohne sofort auszuführen:

const handleClick = type => event => {
  console.log(`Typ: ${type}, Ziel:`, event.target);
};

document.querySelector('#saveBtn')
  .addEventListener('click', handleClick('save'));

So kannst Du denselben Handler mehrfach verwenden – nur mit unterschiedlichen Kontexten.

Wie kann ich Currying rückgängig machen (uncurrying)?

Falls Du aus einer gecurryten Funktion wieder eine Funktion mit mehreren Argumenten machen willst, kannst Du eine kleine Hilfsfunktion schreiben:

function uncurry(fn) {
  return function(...args) {
    return args.reduce((acc, arg) => acc(arg), fn);
  };
}

const curriedAdd = a => b => c => a + b + c;
const normalAdd = uncurry(curriedAdd);

console.log(normalAdd(1, 2, 3)); // → 6

Das kann nützlich sein, wenn Du Currying nur temporär brauchst oder auf APIs stößt, die nicht gecurryte Funktionen erwarten.

Funktioniert Currying auch mit async-Funktionen?

Ja! Currying funktioniert problemlos mit async und await. Jede Rückgabefunktion kann ebenfalls async sein.

const fetchUser = id => async token => {
  const response = await fetch(`https://api.example.com/users/${id}`, {
    headers: { Authorization: `Bearer ${token}` }
  });
  return response.json();
};

const getUserWithToken = fetchUser(42);
getUserWithToken('abc123').then(console.log);

Dieses Pattern ist ideal für APIs, bei denen Du zuerst ein Benutzerobjekt wählst und dann dynamisch den Token zur Authentifizierung hinzufügst.


Buy me a coffee

Wenn Dir meine Beiträge gefallen und sie Dir bei Deiner Arbeit helfen, würde ich mich über einen “Kaffee” und ein paar nette Worte von Dir freuen.

Buy me a coffee



Previous Post
Visitor Pattern in JavaScript - Eine modulare Architektur dank accept Methode
Next Post
Higher-Order Functions in JavaScript - Erklärung mit Praxisbeispielen