I have a series of methods (some asynchronous and some not) that I would like to use bluebird.each to process in order. Here is a dumbed down example:
import bluebird from 'bluebird';
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
class Template {
propertyA!: number;
propertyB!: number;
constructor() {}
async methodA() {
this.propertyA = 10;
console.log({ thisArgInA: this, propertyA: this.propertyA });
await delay(500);
return this.propertyA;
}
methodB() {
this.propertyB = this.propertyA * 100;
console.log({ thisArgInB: this, propertyB: this.propertyB });
return this.propertyB;
}
}
const instance = new Template();
const sequence = [instance.methodA, instance.methodB];
(async function main() {
await bluebird.each(sequence, (fn) => {
const result = fn.call(instance);
console.log({ result });
});
})();
This produces this error I do not understand:
index.ts:27:20 - error TS2684: The 'this' context of type '(() => Promise<number>) | (() => number)' is not assignable to method's 'this' of type '(this: Template) => Promise<number>'.
Type '() => number' is not assignable to type '(this: Template) => Promise<number>'.
Type 'number' is not assignable to type 'Promise<number>'.
27 const result = fn.call(instance);
I thought maybe it is because bluebird.each accepts either values or promise that resolve to values the compiler can't reconcile things, but the only solution I thought of there was to strongly type the return values"
async methodA: Promise<number> { /*...*/ }
methodB: number { /*...*/ }
But that didn't change anything. Also I note the following:
- I have to provide context here, because invoking the functions in the
eachcallbacks loses connection to theTemplateasthis - When
methodAis synchronous (remove theasynckeyword, remove thedelay(), just returnthis.propertyA, this works fine. - When I remove the
returnstatements things work fine. - When I change
fn.call(instance)tofn.bind(instance)()it works fine
How can I satisfy the compiler so it knows how to call these functions with the provided context.
Is there an easier way to call these methods in sequence so their connection to this is maintained?
It may help to look at the inferred types:
sequence:(((this: Template) => number) | ((this: Template) => Promise<number>))[]or just((() => Promise<number>) | (() => number))[]fn:((this: Template) => number) | ((this: Template) => Promise<number>)or just(() => Promise<number>) | (() => number).call:<Template, [], number>(this: (this: Template) => number, thisArg: Template): numberand we can see that the type of the
.call()expression is not what we expect - the compiler wants a function returning anumber, and complains sincefncan alternatively return aPromise<number>. This is probably due to a TypeScript bug when dealing with inferring the return type of function union types. It might be related to the subtle difference betweenX => A | Band(X => A) | (X => B).We can fix this by writing
const result = fn.call<Template,[],number|Promise<number>>(instance);or…(fn: () => number | Promise<number>)) => …orconst sequence:(() => number | Promise<number>)[]