Javascript Promise.all confusion - javascript

I am using Javascript/Typescript in order to format a password value on a collection of PersonModel objects. I am executing a Promise on each element in the Collection.
I am using Promise.all to allow all Promises to complete, and then want to return the formatted collection.
However, I get an error at build time.
PersonService.ts
private decryptPersons(persons: PersonModel[]): Promise<PersonModel[]> {
return new Promise<PersonModel[]>(resolve => {
let promises: Array<Promise<string>> = [];
let decryptedPersons: PersonModel[] = [];
for (let i: number = 0; i < persons.length; i++) {
let ciphertext: string = persons[i].password;
promises.push(this.decrypt(ciphertext).then((password: string) => { // <== line 357
persons[i].password = password;
decryptedPersons.push(persons[i]);
}));
}
Promise.all(promises).then(() => {
resolve(decryptedPersons);
});
});
}
private decrypt(value: string): Promise<string> {
return new Promise<string>(resolve => {
this.encrypter.decrypt(value).then((result: string) => {
resolve(result);
});
});
}
Error
ERROR in ./app/pages/service/personService.ts
(357,31): error TS2345: Argument of type 'Promise<void>' is not assignable to parameter of type 'Promise<string>'.
Type 'void' is not assignable to type 'string'.
I am no expert with Javascript, so my structure may me incorrect. If anyone can advise, I would appreciate it.

You're trying to push the following into your promises array:
this.decrypt(ciphertext).then((password: string) => { // <== line 357
persons[i].password = password;
decryptedPersons.push(persons[i]);
})
but your function here doesn't return anything, so this is going to evaluate to a Promise<void>.
You're also abusing the "explicit promise construction antipattern" in several places here.
Give this a try:
private decryptPersons(persons: PersonModel[]): Promise<PersonModel[]> {
return Promise.all(persons.map(person =>
this.decrypt(person.password)
.then(password => {
person.password = password;
return person;
});
));
}
private decrypt(value: string): Promise<string> {
return this.encrypter.decrypt(value);
}

Related

Why am I am getting "Cannot find name 'function name' error"?

So I added this function below to my ts class:
private get(iD: string): Promise <Function> => async () => {
const tallConfig = await longCrudService.getHalf(iD);
const stream: Stream = tallConfig.stream;
const response = this.createUrl(tallConfig, stream);
return response;
}
But as soon as I add it, every other functions/Methods below it starts throwing this error: "Cannot find name 'function name'". Did I add it the wrong way?. I'm still trying to get a hold of typescript.
private get = async (id: string): Promise<Function> => {
or
private get: (iD: string) => Promise<Function> = async (iD: string) => {
What you are actually declaring here is a method. Declaring methods with arrow syntax goes like this:
private methodName = (param: ParamType): TypeOfReturnable => {
// remember to return something of type TypeOfReturnable
}

How to fix 'object is possibly undefined' and why do I get this error at all?

So Typescript underlines this.ratesMap.get(rate[0]) and tells it's probably undefined. Adding ! solves the problem, I don't get the error anymore but the code just won't work, the output is empty and there's no other errors in the terminal and in the console.
Why typescript underlines this.ratesMap.get(rate[0]) and not this.ratesMap.has(rate[0])? How can I fix this?
export class CardStore implements ICardStore {
public lastRates: ICard[] = [];
#observable
public cardsArray: CurrencyCard[] = []
#observable
private ratesMap: Map<string, CurrencyCard> = new Map<string, CurrencyCard>();
public constructor(#inject(CardApi) private api: CardApi) {
makeObservable(this);
this.getRates();
this.updateRate();
}
#computed
public get recentRates(): CurrencyCard[] {
return this.cardsArray = [...this.ratesMap.values()]
}
private async getRates() {
const rates = await this.api.loadRates();
runInAction(() => {
Object.entries(rates).forEach((rate) => {
if (this.ratesMap.has(rate[0])) {
this.ratesMap.get(rate[0]).update(rate[1]);
} else {
let newCard = new CurrencyCard(rate[0], rate[1]);
this.ratesMap.set(rate[0], newCard);
}
});
});
}
loadrates() from the code above is here:
public async loadRates(): Promise<Record<string, number>> {
return await fetch(
`https://freecurrencyapi.net/api/v2/latest?apikey=MYAPIKEY&base_currency=USD`
)
.then(response => response.json())
.then(data => (data.data));
}
}
The return value of the get method of Map can be undefined (if the map doesn't have an entry for that key). TypeScript doesn't know that you've guarded against that with a has call.
Instead, don't use has (doing has+get is generally an antipattern anyway [a mostly harmless one 🙂], it forces traversal of the map twice). Get the card and check if you got one:
private async getRates() {
const rates = await this.api.loadRates();
runInAction(() => {
Object.entries(rates).forEach((rate) => {
const card = this.ratesMap.get(rate[0]); // ***
if (card) { // ***
card.update(rate[1]); // ***
} else {
let newCard = new CurrencyCard(rate[0], rate[1]);
this.ratesMap.set(rate[0], newCard);
}
});
});
}
TypeScript will be able to narrow the type of card based on if (card), so it will know card is not undefined in the body of the if and will let you use update on it.

Types in object destructing from async function return in Typescript

Given a function that returns an object promise with a boolean & string:
const test = () => {
return new Promise(async (resolve) => {
resolve({ result: false, error: 'This is an error' });
})
}
I try to destruct these couple of values into constants:
const { result, error } = await test();
However, I always get these Typescript errors:
Property 'result' does not exist on type 'unknown'.ts(2339)
Property 'error' does not exist on type 'unknown'.ts(2339)
I've tried all kinds of combinations, but the only one working is adding the 'any' type, which I believe we should always avoid.
Any idea to define the right types without getting error?
Here you should add the type as the generic parameter to the promise - i.e new Promise<MyType>(...) Example:
type MyPromiseResult = { result: boolean, error: string };
const test = () => {
return new Promise<MyPromiseResult>((resolve) => {
resolve({ result: false, error: 'This is an error' });
})
}
async function doThing() {
const { result, error } = await test();
type ResultType = typeof result; // boolean
type ErrorType = typeof error; // string
}
Give your response a type
return new Promise<{ result: boolean, error: string }>((resolve) => {})

How to define an array of a type that includes a generic type variable

I have the following types in Typescript:
type Task<T> = () => Promise<T>;
type QueueItem<T> = { task: Task<T>; resolve: (v: T) => void; reject: () => any };
I have a class that uses these types:
class Queue {
private queue: QueueItem<T>[] = [];
insertTask<T>(task: () => Promise<T>): Promise<T> {
const promise = new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject });
});
return promise;
}
}
Im trying to define a new type that is an array of QueueItem<T>. I have tried:
queue = [] as QueueItem<T>[];
queue: QueueItem<T>[] = [];
queue<T>: QueueItem<T>[] = [];
But none worked. I keep getting the following error:
Cannot find name 'T'.ts(2304)
How can I define it correctly?
You can try it in this demo
You can't have a variable that is typed with an open type parameter. You can use unknown (if T is covariant), never (if T is contravariant) or any (works for T invariant or any other varinance but is less safe)
In your case T appears in both covariant (in task) and in a contravariant position (in position in resolve) so any is the only choice:
type Task<T> = () => Promise<T>;
type QueueItem<T> = { task: Task<T>; resolve: (v:T) => void; reject: () => any };
class Queue {
private queue: QueueItem<any>[] = [];
insertTask<T>(task: () => Promise<T>): Promise<T> {
const promise = new Promise<T>((resolve, reject) => {
this.queue.push({ task, resolve, reject });
});
return promise;
}
}```
[Playground Link](https://www.typescriptlang.org/play/?ssl=14&ssc=2&pln=1&pc=1#code/C4TwDgpgBAKghgZwNYB4YD4oF4oAoCU2mACgE4D2AtgJYIRroDcAUKJFAIoCuEPAksAiUG2KAG8owREgBcsaQ0ZRSEBOQA2ANwhzcmmTEJZMm8tQAmSlQCsIAY2C6jmOADsQUAL4tmzO+sQETh4ecWYoKDBSak04QSgARxCdYN4IASEUNxB0AG0AXVEClgjwqGpXOlJgeGQGXClkJyIoMipaegx8OTaaOhExMoi7ckrgSIo+6BxXCAB3VsmO+twVNS0IABplCFsHZzCIo8kAC1oAOiS087AuBBPcCUakbbWNbVfd+3HPfBKj37-CIqYBcUiuCbtOj-TzMTxAA)
Since you are inserting many different `T` in `queue` there is no way to preserve the types off all of these. If you were to give all the tasks to ever be executed in the `Queue` constructor you could use a tuple type, but that seems to be antithetical to the point of the `Queue`.
Another option is to make `Queue` generic if you just need to forward the type parameter:
```ts
type Task<T> = () => Promise<T>;
type QueueItem<T> = { task: Task<T> };
class Queue<T> {
private queue: QueueItem<T>[] = [
];
}
Playground Link
The Query class is missing the T generic, so typescript do know of which type the queue items are.
class Queue<T> {
private queue: QueueItem<T>[] = [];
}

Issue getting errorCallback from IPromise

I'm working with an existing TypeScript method and I'm struggling to get the errorCallback value from the promise. The Interface looks like the following from the Type Definition file for Angular:
interface IPromise<T> {
then<TResult>(successCallback: (promiseValue: T) => IHttpPromise<TResult>, errorCallback?: (reason: any) => any, notifyCallback?: (state: any) => any): IPromise<TResult>;
then<TResult>(successCallback: (promiseValue: T) => IPromise<TResult>, errorCallback?: (reason: any) => any, notifyCallback?: (state: any) => any): IPromise<TResult>;
then<TResult>(successCallback: (promiseValue: T) => TResult, errorCallback?: (reason: any) => TResult, notifyCallback?: (state: any) => any): IPromise<TResult>;
The TypeScript method I'm working with calls a service and the promise uses the return (this works):
public loadSavedLogin(): ng.IPromise<MyApp.Models.User> {
return this._myAppService.getUser(this.savedUserId).then((result: MyApp.Models.User) => {
if (result) {
this.userId = result.UserID;
this.userName = result.UserName;
}
return result;
});
}
The problem is I have no idea how to get the errorCallback value. If I place a comma after .then((result: MyApp.Models.User), I see Intellisense showing me the errorCallback parameter, but I just can't get any of the syntax working. In raw JS, I'd have a comma at the end with another function accepting the error value, but I'm not sure with this interface how to get the error returned.
How do I modify the function to get the error value if the service call returns one using IPromise?
Here is a simplified example to help you out.
class Test {
public _test: ng.IPromise<string>;
// This method has a return type of ng.IPromise<string>
// You must return a value of this type.
public example(): ng.IPromise<string> {
return this._test.then(
// Success
// Must return a string to be compatible with
// the ng.IPromise<string> return type
(val) => {
alert('Success');
return val;
},
// Error
// Should also return a string to be
// compatible with the return type
(reason) => {
alert('Error: ' + reason);
return '';
});
}
}
Because the example method return type is ng.IPromise<string>, the success function and the error function in the then method must return a string in order for the types to all match up.
In your case, they should return an instance of an MyApp.Models.User.
I suspect in your error function you weren't returning a value - but this makes the best common type between the success and error function void.
Further example... using just an array to show best common types when using functions:
var example = [
(input: string) => { return 'String'; },
(input: string) => { console.log(input); }
];
The best common type used in this example is (input: string) => void. Seems strange - but it actually makes sense. If you call the functions in this array, don't expect to get a return value.
So just make sure your success and error functions have the same return type and all the types will match up for you.
public loadSavedLogin(): ng.IPromise<MyApp.Models.User> {
return this._myAppService.getUser(this.savedUserId).then(
(result: MyApp.Models.User) => {
if (result) {
this.userId = result.UserID;
this.userName = result.UserName;
}
return result;
},
(reason: string) => {
return <MyApp.Models.User> null;
}
);
}

Categories