Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

309
Vistas
How should I go about method chaining in TypeScript (like how Tiptap does it)

Tiptap (and many other text editors based on ProseMirror) has this amazing API where we can chain commands.

For example:

editor.chain().focus().toggleItalic().run()

Here, calling chain() returns all the chainable commands which themselves return chainable commands and that's how the chain goes on and on until you call run().

After calling run(), the commands between chain() and run() are executed in sync.

What's interesting (as far as my knowledge goes) is that this isn't a class. All of this can also be extended (including typings) by creating custom extensions for Tiptap. See this bold extension and its types.

I did go thru there source but wasn't able to comprehend much.

I really liked this design pattern and was wondering how should I go about implementing this in general...

Say, I wanted to implement a calculator on this pattern. How can I make this line work?

calculator.chain().setValue(10).add(3).sub(12).run() // 1

Thank you!

about 4 years ago · Juan Pablo Isaza
2 Respuestas
Responde la pregunta

0

There's lots of ways you could potentially implement this, here is just one with a simple object. A class encapsulation would propbably be a good idea. So long as you keep your methods contained within a single object, you can keep returning it and adding to the callback queue, which will eventually be executed on .run() and break the loop.

const calculator = {
  state: { value: 0 },
  callbacks: [] as ((...args: any[]) => any)[],
  _exec: {
    setValue: (val: number) => (calculator.state.value = val),
    add: (val: number) => (calculator.state.value += val),
    sub: (val: number) => (calculator.state.value -= val),
  },
  _queue: {
    setValue: (val: number) => {
      calculator.callbacks.push(() => calculator._exec.setValue(val));
      return calculator._queue;
    },
    add: (val: number) => {
      calculator.callbacks.push(() => calculator._exec.add(val));
      return calculator._queue;
    },
    sub: (val: number) => {
      calculator.callbacks.push(() => calculator._exec.sub(val));
      return calculator._queue;
    },
    run: () => {
      calculator.callbacks.forEach((cb) => cb());
      calculator.callbacks = [];
    },
  },
  chain: () => calculator._queue,
};

calculator.chain().setValue(10).add(3).sub(12).run();
console.log(calculator.state.value); // logs 1
about 4 years ago · Juan Pablo Isaza Denunciar

0

Just use a class:

class Calculator {
  constructor(value) {
    this.value = value;
  }
  run() { return this.value; }
  add(x) { return new Calculator(this.value + x); }
  sub(x) { return new Calculator(this.value - x); }
  log() { console.log(this.value); return this; }
  static withValue(v) { return new this(v); }
}
const calc = Calculator.withValue(10).add(3).log();
console.log(calc.sub(12).run());
console.log(calc.add(29).run());

The only thing that is important for method chaining is to return an object with all the methods (here: a Calculator instance) from each of the chainable methods. You can achieve the same without class syntax as well:

function calculatorWithValue(v) {
  return {
    run() { return v; },
    add(x) { return calculatorWithValue(v + x); },
    sub(x) { return calculatorWithValue(v - x); },
    log() { console.log(v); return this; },
  };
}
const calc = calculatorWithValue(10).add(3).log();
console.log(calc.sub(12).run());
console.log(calc.add(29).run());

I recommend to keep the objects immutable, to allow creation of multiple independent chains or forking of an existing chain - see the examples above, where the second result is 10 + 3 + 29, not 10 + 3 - 12 + 29.

If you want the execution to be lazy, that's easy as well:

class Calculator {
  constructor(run) {
    this.run = run;
  }
  static withValue(v) { return new this(() => v); }
  add(x) { return new Calculator(() => this.run() + x); }
  sub(x) { return new Calculator(() => this.run() - x); }
  log() { return new Calculator(() => {
    const v = this.run();
    console.log(v);
    return v;
  }); }
}
const calc = Calculator.withValue(10).add(3).log();
console.log(calc.sub(12).run());
console.log(calc.add(29).run());

If you want to make this extensible with some kind of plugin architecture, just put more methods on the prototype, or build the object dynamically. For TypeScript, you'd use declaration merging to amend the Calculator interface with the signatures that your plugin provides.

about 4 years ago · Juan Pablo Isaza Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda