How to make a Promise not re-run code when already resolved? - javascript

I have a User Model:
export class User extends Serializable{
id: string;
first_name: string;
middle_name: string;
last_name: string;
email: string;
image_url: string;
// mykeyStore
static store:StoreDatabase = new StoreDatabase("mykeyStore");
... More code...
and a loadProfile() function to that class which returns a promise.
loadProfile():Dexie.Promise<any>{
let promise = User.store.get('user')
.then(
tuple => {
// Extract User Data from tuple
let user_data = tuple && tuple.value
// Fill User attribute for Tuple's value
for (var attr in user_data) {
this[attr] = user_data[attr];
}
});
return promise;
}
How can I structure my code, so that calling loadProfile will not always run the then if it is already resolved, by calling the following:
let user = new User();
user.loadProfile().then( () =>
console.log(user.first_name)
);

In your case, loadProfile will execute inside code again. To avoid that, you need to store the promise in some variable. For example (es6, cuz i don't know typescript well):
// Execute code inside loadProfile and store the promise in some variable
const loadProfilePromise = user.loadProfile();
// Will show Hurray
loadProfilePromise.then(() => console.log('Hurray');
// Will not execute code inside loadProfile again, but will show 'Hurray2'
setTimeout(() => {
loadProfilePromise.then(() => {
console.log('Hurray2'));
});
}, 100);
And also, do not forget to handle exceptions and rejects in promises, and log them ;D

Every time you call user.loadProfile(), a new promise is created. Therefore, the code in .then() will always run.
If you want to avoid loading attributes multiple times, you can change your function to this:
loadProfile():Dexie.Promise<any>{
if (this.promise) return this.promise
this.promise = User.store.get('user')
.then(
tuple => {
// Extract User Data from tuple
let user_data = tuple && tuple.value
// Fill User attribute for Tuple's value
for (var attr in user_data) {
this[attr] = user_data[attr];
}
});
return this.promise;
}
Then if you call user.loadProfile() when it's already loaded, the code in .then() will be executed immediately.

Related

How to Initialise class properties using inquirer js?

class Example {
constructor(id) {
this.id = this.getId();
}
getId() {
inquirer
.prompt({
message: "Enter id?",
type: "input",
name: "employeesId",
})
.then((answer) => (this.id = answer.employeesId));
}
}
const testExample = new Example();
testExample.getId()
console.log(testExample.id); // <-- expected to log the id after user has entered it, instead returns undefined
So I'm new to OOP and just would like to understand why this won't work, any help would be appreciated.
A work around with explanation would also be highly appreciated.
Thanks in advance.
It takes a while for the then() callback to get called. But when you do this:
testExample.getId()
console.log(testExample.id);
You aren't waiting for the inquirer to finish and set the id. There's no way to wait for it right now because getId() is a void method. It needs to return a Promise.
The best way to do this is with async/await syntax. If you make getId() an async method then that means it will return a Promise that you can await.
It is not possible to definitely set the id to a number in the constructor because the constructor cannot be asynchronous. You can either have this.id be a Promise which resolves to a number or you can have it start out as undefined and then get set to a number once the inquirer is finished.
It looks like you are currently accepting an id as an argument to the constructor but you don't use it, so I'm not sure what's going on there.
With this.id as maybe undefined:
class Example {
constructor() {
}
async getId() {
const answer = await inquirer
.prompt({
message: "Enter id?",
type: "input",
name: "employeesId",
});
this.id = answer.employeesId;
}
}
const testExample = new Example();
await testExample.getId();
console.log(testExample.id);
With this.id as a Promise:
class Example {
constructor() {
this.id = this.getId();
}
async getId() {
const answer = await inquirer
.prompt({
message: "Enter id?",
type: "input",
name: "employeesId",
});
return answer.employeesId;
}
}
const testExample = new Example();
const id = await testExample.id;
console.log(id);

Assign observable within callback in Angular 9

I am completely a newbie to Angular. Some how struggled to get the initial code working now I am stuck here.
Use case : Show loading bar. Fetch merchant list to an observable. Stop loading bar.
Problem:
fetchUsers($event) {
let searchTerm = $event.term;
if (searchTerm.length > 3) {
this.isLoading=true;
this.people$ = null;
this.userService.getMerchantsBySearch(searchTerm);
--> this.isLoading=false;
}
}
this.isLoading becomes false even before getting the response from getMerchantsBySearch call. I know I will need to perform this step in callback but I am not sure how to.
I want to do something like this but I a missing something. What is the correct way to do it.
fetchUsers($event) {
let searchTerm = $event.term;
if (searchTerm.length > 3) {
this.isLoading=true;
this.people$ = null;
this.userService.getMerchantsBySearch(searchTerm).subscribe(
res={
---> this.people$ =res;
---> this.isLoading=false;
}
);
}
}
dashboard.component.ts
people$: Observable<IMerchant[]>;
fetchUsers($event) {
let searchTerm = $event.term;
if (searchTerm.length > 3) {
this.isLoading=true;
this.people$ = null;
this.userService.getMerchantsBySearch(searchTerm);
this.isLoading=false;
}
}
user.service.ts
getMerchantProfile(id){
var options = this.getOptions();
return this.http.get<IMerchant[]>(environment.APIBaseURL+"/getMerchantProfile/"+id,options);
}
merchant.model.ts
export interface IMerchant{
email:String,
mobile : String,
password: String,
businessName : String,
otp:Number,
info:string,
url:String
}
i prefer using the teardown method .add(). it doesn't pollute your pipe chain of data manipulation and is also executed after an error is handled:
this.userService.getMerchantsBySearch(searchTerm)
.pipe(
// manipulate data here
)
.subscribe(
res => {
// handle final data
},
error => {
// handle error
}
).add(() => {
// teardown here (e.g. this.isLoading=false;)
});
By the way: if this.people$ is an observable, you can not assign it inside the subscribe callback. you'll need to assign it to the response of the userService method you are using. Be careful not to call subscribe on the stream since it will return you a subscription instead of an observable:
this.people$ = this.userService.getMerchantsBySearch(searchTerm).pipe(...)
You can actually pipe it and add a finalize
this.userService.getMerchantsBySearch(searchTerm)
.pipe(finalize(() => this.isLoading=false))
.subscribe(
res => {
this.people$ =res;
});

Get First Index in Array Object Type Scripts

I want to get index zero record from Array Object dataList in the below code snippet. I am new to JavaScript and Typescript.
dataList: Array<any> = [];
data: any;
constructor(private apiService: ApiService) {
this.apiService.getEncounterDashBoard(this.searchCond)
.subscribe(data => {
this.dataList = data.result;
});
let labels = this.dataList[0];
}
you can try like this
dataList: Array<any> = [];
data: any;
constructor(private apiService: ApiService) {
let labels = [];
this.apiService.getEncounterDashBoard(this.searchCond)
.subscribe(data => {
this.dataList = data.result;
labels = this.dataList[0];
// here you can see that we are access the data inside the subscription block bcs javascript is asynchronous. So it is not wait complete the api call
});
}
Think of async operations as those that occur after the rest.
Your getEncounterDashBoard is called and starts the request, but everything in your code continues, with or without response (usually without as everything is too quick).
Therefore, your let labels is trying to get this.dataList[0] before you actually have a response. One thing you can do, is create a component-scoped variable labels, and then asign it inside the callback of the async function (inside the subscribe), this way this happens after the async function has resolved.
Another option, is create a function that handles the logic you want to happen after the async is resolved, and call it inside the subscribe.
afterDataList() {
let labels = this.dataList[0]
// do something with it
// ...
}
Place the line inside the subscribe function.
.subscribe(data => {
this.dataList = data.result;
let labels = this.dataList[0];
});
You can also use ES6 destructuring:
.subscribe(data => {
this.dataList = data.result;
let [labels] = this.dataList;
});

Angular: pass a method as parameter

I have these two methods which are almost similar:
private firstFunction () {
this.serviceOne.methodOne().subscribe(
res => {
return resultOne = res;
},
err => {}
);
}
private secondFunction () {
this.serviceTwo.methodTwo().subscribe(
res => {
return resultTwo = res;
},
err => {}
);
}
I want to write a generic function, like this:
genericFunction (service ,method , result ) {
service.method().subscribe(
res => {
return result = res;
},
err => {}
);
}
And consequently I want to get something like this working:
genericFunction (serviceOne , methodOne , resultOne );
genericFunction (serviceTwo , methodTwo , resultTwo );
Actually, I cannot find how to pass methodOne and methodTwo as params. Any sugestions?
There are several issues in your code.
Firstly, you want to modify the field you pass in as a parameter (as suggested by result = res. You can't pass in a reference to a field, but you can pass in the field name, and use indexing to change the field. keyof T will allow you to pass in the field in a type safe way.
Secondly if you want to access a method on a service. Again we can do this passing in the method name, and we can constrain the service to have a method with the passed in method name, that returns an Observable. The result of the Observable can also be constrained to be of the same type of the field we are going to assign it to in order for the method to be fully type safe.
declare class Service1 {
method1() : Observable<number>
}
declare class Service2 {
method2() : Observable<string>
}
class MyClass {
resultOne!: number;
resultTwo!: string;
constructor() {
this.genericFunction(new Service1(), "method1", "resultOne");
this.genericFunction(new Service2(), "method2", "resultTwo");
this.genericFunction(new Service1(), "method1", "resultTwo"); // error resultTwo is a string, the method return Observable<number>
this.genericFunction(new Service2(), "method", "resultTwo"); // error method does not exit on Service2
this.genericFunction(new Service2(), "method2", "resultTwo2"); // error field does not exist on type
}
genericFunction<MethodKey extends string, ResultKey extends keyof MyClass>(service:Record<MethodKey, ()=> Observable<MyClass[ResultKey]>>, method:MethodKey, result: ResultKey){
service[method]().subscribe(
res => this[result] = res,
err => {}
);
}
}
Note We could have also passed in the function as a function not just as a name, but directly a typed function. The disadvantage of this is that we either have to use bind to ensure the service method will still have the correct this when it's called, or use an arrow function when calling (again to ensure the service method has the correct this). This is error prone though, bind results in an untyped function, so we can't check compatibility to the field, and someone might pass service.method directly and no error would be reported until runtime:
class MyClass {
resultOne!: number;
resultTwo!: string;
constructor() {
var service1 = new Service1()
var service2 = new Service2()
this.genericFunction(()=> service1.method1(), "resultOne");
this.genericFunction(()=> service2.method2(), "resultTwo");
this.genericFunction(service2.method2, "resultTwo"); // no error, depending on the implementation of method2 it might or might not work
this.genericFunction(service2.method2.bind(service2), "resultOne"); // no error, the service call will work, but we store it in an incompatible variable
this.genericFunction(()=> service1.method1(), "resultTwo");// error resultTwo is a string, the method return Observable<number>
this.genericFunction(()=> service2.method2(), "resultTwo2");// // error field does not exist on type
}
genericFunction<MethodKey extends string, ResultKey extends keyof MyClass>(method:()=> Observable<MyClass[ResultKey]>, result: ResultKey){
method().subscribe(
res => this[result] = res,
err => {}
);
}
}
try by using the following code:
private firstFunction () {
let response= genericFunction(this.serviceOne.methodOne())
}
private secondFunction () {
let response = genericFunction(this.serviceTwo.methodTwo())
}
Modify you Generic Function by just receiving a variable.
//if it is angular 4 or less
genericFunction (method: Observable) {
return method.map(res => {
return res.json();
});
}
//if it is angular 5 or 6
genericFunction (method: Observable) {
return method.pipe(
map(res => {
return res;
}));
}

Flow Types with Promises (Fetch's)

I created a Fetch function to consume a JSON API and have defined types for the JSON object. I am confused about how to define the return type for the getCurrentJobAPI function since I do a bunch of .then() afterwards. Is the return value the last .then()? In my code, the last .then() is a setState, so what would the type be for that?
getCurrentJobAPI = (): {} => {
const url: string = `dummy_url&job_id=${this.props.currentJob}`;
return fetch(url, {credentials: 'include'})
.then((response) => {
return response.json();
})
.then((json: CurrentJob) => {
console.log(json);
const location = json.inventoryJob.location;
const ref_note = json.inventoryJob.note;
const id = json.inventoryJob.id;
const models = json.inventoryJobDetails.map((j) => {
return Object.assign({}, {
code: j.code,
qty: j.qty
})
});
this.setState({ currentCodes: models, location: location, ref_note: ref_note, id: id})
return json
})
.then((json: CurrentJob) => {
const barcodes = json.inventoryJob.history;
if (barcodes.length > 0) {
this.setState({apiBarcodes: barcodes})
}
this.calculateRows();
this.insertApiBarcodes();
this.setState({ initialLoad: true });
})
};
UPDATE:
Although I understand that I am supposed to define Promise<type> as the return value of getCurrentJobAPI (see Gilad's answer and comments), I am still unsure why I can't write Promise<CurrentJob> if the Fetch resolves as the JSON response.
[I have condensed my .then() statements per loganfsmyth's recommondation.]
Here are the type definitions for CurrentJob:
type Job = {
user_id: number,
status: 'open' | 'closed',
location: 'string',
history: {[number]: string}[],
note: string,
} & CommonCurrentJob;
type JobDetails = {
iaj_id: number,
code: number,
} & CommonCurrentJob;
type CommonCurrentJob = {
id: number,
qty: number,
qty_changed: number,
created_at: string,
updated_at: string
}
So first off, a disclaimer, I am a TypeScript user but I find that this question is actually applicable to both languages and has the same answer.
I created a Fetch function to consume a JSON API and have defined types for the JSON object. I am confused about how to define the return type for the getCurrentJobAPI function since I do a bunch of .then() afterwards. Is the return value the last .then()? In my code, the last .then() is a setState, so what would the type be for that?
TL;DR: Promise<void> (see note). As you suspect, this is in fact the return type of the last top-level .then in the promise chain.
Now lets dig a bit deeper
Here is your example, reworked very slightly to leverage type inference instead of annotating callback parameters that are declared as any by their receivers.
As an aside, these callback parameter annotations amount to unsafe implicit casts, or type assertions as we call them in TypeScript, and they lie about the shape of the code. They look like this
declare function takesFn(fn: (args: any) => any): void;
So I have minimized these since they form a subtle trap
// #flow
import React from 'react';
type CurrentJob = {
inventoryJob: Job,
inventoryJobDetails: JobDetails[]
}
export default class A extends React.Component<{currentJob:JobDetails}, any> {
getCurrentJobAPI: () => Promise<void> = () => {
const url = `dummy_url&job_id=${String(this.props.currentJob)}`;
return fetch(url, {credentials: 'include'})
.then(response => {
return (response : {json(): any}).json();
}) // --> Promise<any>
.then(json => {
const currentJob = (json: CurrentJob); // make the assumption explicit.
console.log(currentJob);
const {location, id, note: ref_note} = currentJob.inventoryJob;
const currentCodes = currentJob.inventoryJobDetails
.map(({code, qty}) => ({
code,
qty
}));
this.setState({currentCodes, location, ref_note, id});
return currentJob;
}) // --> Promise<CurrentJob>
.then(currentJob => {
const apiBarcodes = currentJob.inventoryJob.history;
if (apiBarcodes.length > 0) {
this.setState({apiBarcodes});
}
this.setState({initialLoad: true});
}); // --> Promise<void>
};
}
So I am making assertions about the promises in each then call above but those assertions are all validated by type inference with the exception of the initial type cast on the response value.
As further evidence, if we remove the type declaration from the getCurrentJobAPI property of A, flow will infer that its type is in fact Promise<void>.
Bonus: simplifying with async/await. I've used several ESNext features above to shorten the code and make it a bit more pleasant, but we can leverage a specific feature, async/await to make it easier to understand control flow and types in Promise based code.
Consider this revision.
// #flow
import React from 'react';
type CurrentJob = {
inventoryJob: Job,
inventoryJobDetails: JobDetails[]
}
export default class A extends React.Component<{currentJob:JobDetails}, any> {
getCurrentJobAPI = async () => {
const url = `dummy_url&job_id=${String(this.props.currentJob)}`;
const response = await fetch(url, {credentials: 'include'});
const json = await response.json();
const currentJob = (json: CurrentJob); // make the assumption explicit.
console.log(currentJob);
const {location, id, note: ref_note} = currentJob.inventoryJob;
const currentCodes = currentJob.inventoryJobDetails.map(({code, qty}) => ({
code,
qty
}));
this.setState({currentCodes, location, ref_note, id});
const apiBarcodes = currentJob.inventoryJob.history;
if (apiBarcodes.length > 0) {
this.setState({apiBarcodes});
}
this.setState({initialLoad: true});
};
}
Clearly, this is a void function. It has no return statements. However, as an async function, it inherently returns a Promise, just as it did when written as an explicit Promise chain.
Note: void is a construct that has been found useful in Flow and TypeScript to represent the semantic intent of function that do not return values but in reality such functions actually return undefined because, well, this is JavaScript. Flow does not seem to recognize undefined as a type, but under TypeScript, the function could equally be annotated as returning Promise<undefined>. Irregardless, Promise<void> is preferable thanks to the clarity of intent it provides.
Remarks: I worked through this using a combination of https://flow.org/try and the flow binary for Windows. The experience on Windows is really terrible and hopefully it will improve.
When chaining then's, the result will always be a promise.
When calling then, the return value is another promise, otherwise chaining then's wouldn't have been possible.
You can see that easily by using console.log() surrounding the entire chain.

Categories

Resources