1

In javascript if i have a function defined like so

function Person() {
}

Person.prototype.someFunction = function() {
  // Does soome logic
  return this
}

Person.prototype.anotherFunction = function () {
  // Does soome logic
  return this;
};


And i want to implement chaining i will do something like this

const person = new Person();

person.someFunction().anotherFunction()

And this works as each method returns the instance of Person.

Now if i have a method which has some async action how do i return the this instsance in an async method

function someApiCall() {
  return new Promise((res) => {
    setTimeout(() => {
      res('got data');
    }, 2000);
  });
}


Person.prototype.asyncFunction = function () {
  someApiCall()
    .then()
    .catch()

  // HOW DO I RETURN THIS INSTANCE HERE ???? as someAPICALL is async
};

So that i can use it as

person.someFunction().asyncFunction().anotherFunction()
Nitish Phanse
  • 552
  • 1
  • 9
  • 16
  • You can never literally do it as you say, unfortunately, because `person.someFunction().asyncFunction()` is a Promise, which won't have the `.anotherFunction` method. The best you could do would be to be able todo something like `person.someFunction().asyncFunction().then(anotherFunction)` – Robin Zigmond Aug 05 '21 at 21:50
  • You can just `return this` again, but that means you cannot wait for `someApiCall()`. If you want to wait, you need to change the chained call (e.g. into `(await person.someFunction().asyncFunction()).anotherFunction()`). – Bergi Aug 05 '21 at 22:01
  • Just out of curiosity: why would you still have code like this instead of modern class syntax? Having said that, if you're writing async code, you need to give up the idea of instance chaining: you're now working with promises, and you need to think in terms of _promise chaining_ instead, so you'll never have `object.dothing().doanotherthing()`, now you either write normal code but with the `await` keyword, or you use `object.firstthing().then(...).then(...).then(...)`. – Mike 'Pomax' Kamermans Aug 05 '21 at 22:43
  • 1
    @RobinZigmond sorry but that's not true – Mulan Aug 06 '21 at 20:10

3 Answers3

2

Option 1 (Not executed in order):

Person.prototype.asyncFunction1 = function () {
  someApiCall()
  .then((e) => console.log(e))
  .catch()

  return this;
};

p.anotherFunction().asyncFunction1().anotherFunction()

All functions get called, but not in order. If you want to execute it in order, just do it like this:

Option 2 (Executed in order):

Person.prototype.asyncFunction2 = async function () {
  const ans = await someApiCall();
  console.log(ans);
  return this;
};

// t represents this as you return it in asyncFunction2 
p.anotherFunction().asyncFunction2().then((t) => t.anotherFunction())
fabian
  • 268
  • 3
  • 15
1

You're trying to apply a synchronous programming paradigm to an asynchronous code approach, and that's just not going to work. When working with promises, which is what async does for you in an automatic way, rather than instance chaining, your code logic now needs to deal with promise chaining instead.

First, let's stop using legacy prototype syntax and look at modern (where "modern" is over five years old by now) class syntax:

class Person {
  async someFunction() {
    return ...
  }

  async anotherFunction() {
    return ...
  }
}

Because async is just a convenient promise wrapping, we have two options:

const person = new Person();
person
  .someFunction()
  .then(result => {
    person
      .anotherFunction()
      .then(result => ...);
      .catch(e => console.error(e));
  })
  .catch(e => console.error(e));

but this is both cumbersome and ugly. Let's use awaits instead:

const person = new Person();
try {
  const someResult = await person.someFunction();
  const anotherResult = await person..anotherFunction();
  ...
} catch (e) {
  console.error(e);
}

Much better. We don't need instance chaining anymore when we're using async patterns, it's a pattern from a previous era of JS, and writing modern code does not benefit from trying to force it back in.

Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
0

Some people are telling you that it can never be done or it's just not going to work. It's not their fault for misunderstanding but you don't need to suffer the same way as them.

Let' say you have an ordinary class, Account, with a few async methods -

class Account {
  constructor(balance) {
    this.balance = balance
  }
  async withdraw (x) {
    await sleep(1000)
    this.balance -= x
  }
  async deposit (x) {
    await sleep(1000)
    this.balance += x
  }
}

sleep is a simple function which delays the program for ms milliseconds -

const sleep = ms =>
  new Promise(r => setTimeout(r, ms))

Now we can write a chain function -

const chain = t =>
 new Proxy(Promise.resolve(t), { get: get(t) })

const get = t => (target, prop) => (...args) => 
  prop === "then"
    ? target[prop](...args)
    : chain(target.then(async v => (await v[prop](...args), v)))

This seemingly allows us to mix synchronous and asynchronous behaviour -

const A = new Account(100)
const B = new Account(200)

chain(A).deposit(5).withdraw(20).then(a => console.log("A", a))
chain(B).withdraw(20).withdraw(30).deposit(10).then(b => console.log("B", b))

Run the snippet below to verify the result in your own browser -

const sleep = ms =>
  new Promise(r => setTimeout(r, ms))

const get = t => (target, prop) => (...args) => 
  prop === "then"
    ? target[prop](...args)
    : chain(target.then(async v => (await v[prop](...args), v)))

const chain = t =>
 new Proxy(Promise.resolve(t), { get: get(t) })
  
class Account {
  constructor(balance) {
    this.balance = balance
  }
  async withdraw (x) {
    await sleep(1000)
    this.balance -= x
  }
  async deposit (x) {
    await sleep(1000)
    this.balance += x
  }
}

const A = new Account(100)
const B = new Account(200)

chain(A).deposit(5).withdraw(20).then(a => console.log("A", a))
chain(B).withdraw(20).withdraw(30).deposit(10).then(b => console.log("B", b))
console.log("running...")
A { balance: 85 }
B { balance: 160 }

Invent your own convenience. It's your program to do with as you please.

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 1
    I didn't say it was impossible, but I did say that applying the jquery paradigm to modern async JS isn't going to work, because it isn'y: you're going to have to write lots of code like what you wrote you here for no benefit at all, so I'll tell you the same thing: this is _incredibly_ silly given that you can just await and call your next function. jQuery was its own thing, with its own conventions, and just because with enough ninja hacks "you can", that doesn't mean it makes sense. – Mike 'Pomax' Kamermans Aug 06 '21 at 20:39
  • @Mike what do you mean "lots of code"? `chain` is just a few lines and works for any object-oriented datum with asynchronous methods. If a small function can allow you to abstract behaviour and change how you reason about your program, I don't see what's silly about that. And it's not a "ninja hack," it's a well-defined and easy-to-test function. – Mulan Aug 06 '21 at 21:47
  • The silly part is forcing the jQuery paradigm back into modern JS, which _doesn't need that paradigm_. Just await a thing, then carry on, there is no reason for instance chaining in modern JS. Even if, of course, you can put that in if you _really_ wanted to for some reason. Programming languages, and the established conventions for how best to use them, change over time: change how you use them along with how they change. – Mike 'Pomax' Kamermans Aug 07 '21 at 03:11
  • `chain` (above) is not designed with respect to jQuery and its patterns. There are many good object-oriented dot-chaining api that have nothing to do with jQuery. `arr.filter(...).reduce(...)`, `document.querySelector(...).addEventListener(...)`, `expect(actual).not.toBe(expected)`, `client.db(...).collection(...).limit().find()`, and many more. There's no rule that says "You cannot design a dot-chaining api for asynchronous programs". Firebase is another prime example of a sync-feeling api that automatically handles all the async for you. These are not "ninja hacks." – Mulan Aug 07 '21 at 13:14
  • Above all, JS is a multi-paradigm language, meaning it supports a wide variety of syntax, data structures and patterns. `chain` is shown here as one example of how to build a sync-feeling api that does async for you, and it's done in just a few lines of code. I don't know precisely how these other api are implemented, but that's actually the beauty of abstraction: a module (or even a language) is designed for the programmer to think about and solve the problem in useful and effective ways. _How_ the work is done, async or otherwise, is in the module's domain, and your mind is free. – Mulan Aug 07 '21 at 13:27