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()
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())
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.
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.