How to wait for an API call using subscribe to finish in ngOnInit? - node.js

Actual Log order:
('ngOnInit started')
('after me aaya', this.policydetails)
('Here', Object.keys(this.policy).length)
Expected Log order:
('ngOnInit started')
('Here', Object.keys(this.policy).length)
('after me aaya', this.policydetails)
Component.ts file snippet below:
ngOnInit() {
console.log('ngOnInit started');
this.route.params.subscribe(params => {
this.getPoliciesService.getPolicyDetails(params.policyNo)
.subscribe((data: PoliciesResponse) => {
this.policy = data.data[0];
this.flattenPolicy();
console.log('Here', Object.keys(this.policy).length);
});
});
this.makePolicyTable();
}
ngAfterViewInit() {
console.log('after me aaya', this.policydetails);
const table = this.policydetails.nativeElement;
table.innerHTML = '';
console.log(table);
console.log(this.table);
table.appendChild(this.table);
console.log(table);
}
Service.ts file snippet below:
getPolicyDetails(policyNo) {
const serviceURL = 'http://localhost:7001/getPolicyDetails';
console.log('getPolicyDetails service called, policyNo:', policyNo);
const params = new HttpParams()
.set('policyNo', policyNo);
console.log(params);
return this.http.get<PoliciesResponse>(serviceURL, {params} );
}
JS file snippet corresponding to the API call below:
router.get('/getPolicyDetails', async function(req, res) {
let policyNo = (req.param.policyNo) || req.query.policyNo;
console.log('policyNo', typeof policyNo);
await helper.getPolicyDetails({'policyNo' : policyNo},
function(err, data) {
console.log(err, data)
if (err) {
return res.send({status : false, msg : data});
}
return res.send({status : true, data : data});
});
});
Can anyone please suggest where exactly do i need to async-await for expected log order?

If you want this.makePolicyTable() to be called only after the web request (getPolicyDetails) completes, then you should place that call inside of the .subscribe() block:
ngOnInit() {
console.log('ngOnInit started');
this.route.params.subscribe(params => {
this.getPoliciesService.getPolicyDetails(params.policyNo)
.subscribe((data: PoliciesResponse) => {
this.policy = data.data[0];
this.flattenPolicy();
console.log('Here', Object.keys(this.policy).length);
this.makePolicyTable();
});
});
}
You'll probably also want to move the table logic that's in ngAfterViewInit() inside the subscribe() block, too.
Basically, any logic that needs to wait for the asynchronous call to complete should be triggered inside the .subscribe() block. Otherwise, as you're seeing, it can be run before the web request gets back.
Finally, I would move this web service call into ngAfterViewInit() instead of ngOnInit(). Then you can be sure that the Angular components and views are all set up for you to manipulate them when the web service call completes.

You could also set a flag variable to false in the component and then when async calls finishes set it to true and render the HTML based on that flag variable with *ngIf syntax.

Related

Calling Direct Method and Handling Results from Azure Function

I am working on a simple IoT application that needs to be able to take sensor measurements on demand. I am using a serverless architecture where a simple button click on a static site triggers an Azure function. This function should invoke a direct method on a given IoT device and store the results in Cosmos DB. However, when this function executes (it executes successfully) the callback function for invokeDeviceMethod never executes. Instead, context.log("Somehow I got here without a callback") is logged and the function returns 200 success. The line just inside the if statement that logs req.query.method and req.query.device shows the expected values for both of those parameters. My device never receives the request to invoke the method and as such the callback is never executed. What am I missing in order to invoke a given method from an Azure function? I don't get any errors when running this as a function and calling the direct method from the Azure portal using the 'direct method' button on the device page works well. Thank you in advance for any assistance!
My azure function code is below.
'use strict';
const https = require('https');
var Client = require('azure-iothub').Client
//var Protocol = require('azure-iot-device-mqtt').Mqtt;
var conn_str = "<CONNECTION_STRING>"
module.exports = async function (context, req) {
//context.log('JavaScript HTTP trigger function POSTMeasure processed a request.');
if (req.query.method && req.query.device) {
context.log("Direct Method Invocation! Calling "+req.query.method + " on " + req.query.device)
var methodParams = {
methodName: req.query.method,
payload: {},
connectTimeoutInSeconds: 15,
responseTimeoutInSeconds: 15
};
context.log(methodParams);
var client = Client.fromConnectionString(conn_str)
client.invokeDeviceMethod(req.query.device, methodParams, function(err, result) {
context.log("IN CALLBACK");
if (err) {
context.log("Direct method error: "+err.message);
context.res = {
status: 500, /* Defaults to 200 */
body: "Failed " + req.query.method + " on " + req.query.device
};
} else {
context.log("Successfully invoked the device to read dht11.");
console.log(JSON.stringify(result, null, 2));
context.res = {
// status: 200, /* Defaults to 200 */
body: "Suceeded " + req.query.method + " on " + req.query.device
};
}
});
context.log("Somehow I got here without a callback");
}
else {
context.res = {
status: 400,
body: "Please pass a method and device on the query string"
};
}
};
Your issue is that the function you pass to invokeDeviceMethod is not going to be executed until the call is complete. It is essentially a callback function to provide the result. Having called invokeDeviceMethod function JavaScript will execute the next line which is the "Somehow I got here...". You will need to modify your code to wait for the invokeDeviceMethod to complete and execute your callback. Now I'm no JavaScript programmer but you could, for example, wrap the call in a promise and wait on it. That way your function will delay until the call is finished. Primitive example of how you might do this:
// Target function to wrap in promise for demonstration purposes
function test(callback)
{
setTimeout(() => { callback(null, 'good') }, 4000);
}
// Wraps test in a promise
function testPromise()
{
var p = new Promise((resolve, reject) =>
{
test((err, result) =>
{
if (err)
{
reject(err);
}
else
{
resolve('good');
}
});
});
return p;
}
// Will not advance until the wrapped function has completed
testPromise()
.then((result) => { console.log(result) })
.catch(() => { console.log("failed"); } )

How to returned poll data after each nodejs api call to reactjs component

I need to poll the data until the response.Status === 'UpdatesComplete'.
I have written this node js API function which basically polls the data -
const getResults = async (location) => {
try {
const response = await poll(location);
if (response.Status && response.Status === 'UpdatesComplete') {
return response;
}
return await getResults(location);
} catch (err) {
throw err;
}
};
app.get('/url', async (req, res) => {
try {
const results = await getResults(req.query);
res.json(formatData(results));
} catch (err) {
res.status(500).send(err);
console.error(err);
}
});
I am calling this API from ReactJS class component inside ComponentDidMount lifecycle method -
componentDidMount = () => {
axios.get('url', {
params: params
})
.then(response => {
console.log(response.data, 'data');
// setting the data on state
this.setState({ filteredList: response.data });
})
.catch(err => {
this.setState({ error: true });
});
};
This is working fine. But since the API is returning data till all the data has been fetched(after doing polling), it's taking a very long time on view to load the data. I am basically looking for returning the polling data to view as soon as the data fetched and simultaneously polling the API until all the data has been fetched. In this case, data will keep on updating after each polling on the view which will improve the user experience.
Thanks in advance.
You are finding the lazy loading or infinite scroll solution on the server-side. There is no simple way to do this.
The basic idea of the solution is to paginate your result with polling. ie.
call url?size=2&offset=0 from the client-side. Then on the server-side just poll first 2 results and return. next time call url?size=2&offset=2 and so-on.

Rxjs in nestjs - Observable subscription error

I am newbie trying out rxjs and nestjs. The use case that I am currently trying to accomplish is for educational purpose. So I wanted to read a json file (throw an observable error in case of the file being empty or cannot be read) using the "fs" module. Now I create an observable by reading the file asynchronously, set the observer in the subject and then subscribe to the subject in the controller. Here is my code in the service
#Injectable()
export class NewProviderService {
private serviceSubject: BehaviorSubject<HttpResponseModel[]>;
// this is the variable that should be exposed. make the subject as private
// this allows the service to be the sole propertier to modify the stream and
// not the controller or components
serviceSubject$: Observable<HttpResponseModel[]>;
private serviceErrorSubject: BehaviorSubject<any>;
serviceErrorSubject$: Observable<any>;
filePath: string;
httpResponseObjectArray: HttpResponseModel[];
constructor() {
this.serviceSubject = new BehaviorSubject<HttpResponseModel[]>([]);
this.serviceSubject$ = this.serviceSubject.asObservable();
this.serviceErrorSubject = new BehaviorSubject<any>(null);
this.serviceErrorSubject$ = this.serviceErrorSubject.asObservable();
this.filePath = path.resolve(__dirname, './../../shared/assets/httpTest.json');
}
readFileFromJson() {
return new Promise((resolve, reject) => {
fs.exists(this.filePath.toString(), exists => {
if (exists) {
fs.readFile(this.filePath.toString(), 'utf-8' , (err, data) => {
if (err) {
logger.info('error in reading file', err);
return reject('Error in reading the file' + err.message);
}
logger.info('file read without parsing fg', data.length);
if ((data.length !== 0) && !isNullOrUndefined(data) && data !== null) {
// this.httpResponseObjectArray = JSON.parse(data).HttpTestResponse;
// logger.info('array obj is:', this.httpResponseObjectArray);
logger.info('file read after parsing new', JSON.parse(data));
return resolve(JSON.parse(data).HttpTestResponse);
} else {
return reject(new FileExceptionHandler('no data in file'));
}
});
} else {
return reject(new FileExceptionHandler('file cannot be read at the moment'));
}
});
});
}
getData() {
from(this.readFileFromJson()).pipe(map(data => {
logger.info('data in obs', data);
this.httpResponseObjectArray = data as HttpResponseModel[];
return this.httpResponseObjectArray;
}), catchError(error => {
return Observable.throw(error);
}))
.subscribe(actualData => {
this.serviceSubject.next(actualData);
}, err => {
logger.info('err in sub', typeof err, err);
this.serviceErrorSubject.next(err);
});
}
Now this is the controller class
#Get('/getJsonData')
public async getJsonData(#Req() requestAnimationFrame,#Req() req, #Res() res) {
await this.newService.getData();
this.newService.serviceSubject$.subscribe(data => {
logger.info('data subscribed', data, _.isEmpty(data));
if (!isNullOrUndefined(data) && !_.isEmpty(data)) {
logger.info('coming in');
res.status(HttpStatus.OK).send(data);
res.end();
}
});
}
The problem I face is that I can get the file details for the first time and the subscription is getting called once > its working fine. On the subsequent requests
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:470:11)
at ServerResponse.header (C:\personal\Node\test-nest.js\prj-sample\node_modules\express\lib\response.js:767:10)
at Ser
and the endpoint /getJsonData results in an error. Could someone help me out. i believe the subscription is not getting properly after the first call, but not sure how to end that and how to resolve that
The problem is that you're subscribing to your serviceSubject in your controller. Every time a new value is emitted, it will try to send the response. This works the first time, but the second time it will tell you it can't send the same response again; the request has already been handled.
You can use the pipeable first() operator to complete the Observable after the first value:
#Get('/getJsonData')
public async getJsonData() {
await this.newService.getData();
return this.newService.serviceSubject$.pipe(first())
}
You want your Observable to be shared (hot), so that every subscriber always gets the same, latest value. That's exactly what a BehaviourSubject does. So you should not convert your Subject to an Observable when you expose it publicly because you will lose this desired behavior. Instead, you can just cast your Subject to Observable, so that internally it is still a subject but it will not expose the next() method to emit new values publicly:
private serviceSubject: BehaviorSubject<HttpResponseModel[]>;
get serviceSubject$(): Observable<HttpResponseModel[]> {
return this.serviceSubject;
}
I think trying to convert the cold observable (the one that I created) to a hot/warm observable might help to plugin to a single source and emit and complete its execution and maintain the last emitted data to any cloned values. So I make the cold observable to a warm observable using the publishLast(), refCount() operators, and I could achieve the single subscription and the execution completion of the observable. Here are the change I made to work.
This is the service class change I made
getData() {
return from(this.readFileFromJson()).pipe(map(data => {
logger.info('data in obs', data);
this.httpResponseObjectArray = data as HttpResponseModel[];
return this.httpResponseObjectArray;
}), publishLast(), refCount()
, catchError(error => {
return Observable.throw(error);
}));
// .subscribe(actualData => {
// this.serviceSubject.next(actualData);
// }, err => {
// logger.info('err in sub', typeof err, err);
// this.serviceErrorSubject.next(err);
// });
}
And this is the change I made in the controller
public async getJsonData(#Req() req, #Res() res) {
let jsonData: HttpResponseModel[];
await this.newService.getData().subscribe(data => {
logger.info('dddd', data);
res.send(data);
});
}
Any answers that allow the observables to be first subscribed to subjects and then subscribing that subject in the controller is also welcome.
I found a great post on hot vs cold observables and how to make an observable subscribe to a single source and convert a cold, to a hot/warm observable - https://blog.thoughtram.io/angular/2016/06/16/cold-vs-hot-observables.html
I would recommend to return the Promise directly to the controller. Here, you don't need an Observable. For the subscribers, you additionally emit the value of the Promise to your serviceSubject.
async getData() {
try {
const data = await this.readFileFromJson();
this.serviceSubject.next(data as HttpResponseModel[]);
return data;
} catch (error) {
// handle error
}
}
In your controller you can just return the Promise:
#Get('/getJsonData')
public async getJsonData() {
return this.newService.getData();
}

return value from nodejs function

Please, help me
I have a script
export function GetKey(inn, res) {
try {
const body = {
7709798583: {
name: 'someName',
key: '123'
},
7718266352: {
name: 'otherName',
key: '123'
}
};
res(body[inn]['key']);
} catch (err) {
res('0000000000000');
}
};
In other file I try to use this function
GetKey(param, (name) => {
console.log(name);
});
It's ok. but I need to return callback to the parametr. How?
var param = GetKey(param, (name) => {
return name;
});
is not correct and return undefined
That's not how callbacks work; although, you can fake that behavior using Promise and async-await syntax.
If you want to write your code like you have it, you'll want to put the rest of your logic in a function and pass it directly to your callback:
var param = ''
var allYourLogic = name => {
// YOUR LOGIC
param = name
}
GetKey(param, allYourLogic);
Or you can simply inline your logic:
GetKey(param, (name) => {
param = name
// YOUR LOGIC
});
Using the Promise syntax, this is how it looks:
new Promise(resolve => {
GetKey(param, resolve)
})
.then(name => {
param = name
// YOUR LOGIC
})
Lastly, using the async-await methodology:
var param = (
await new Promise(resolve => {
GetKey(param, resolve)
})
)
Really though, it seems like you're doing something wonky which is why you're running into this issue in the first place.
Your entire application will act like it's asynchronous as soon as you use a callback because the callback doesn't execute immediately in Node.js's event loop. Instead, the current function you're in will finish executing before the GetKey function calls the callback method.

How do I test this async method call in reactjs using mocha

// Balance.jsx
...
updateToken () {
const parseResponse = (response) => {
if (response.ok) {
return response.json()
} else {
throw new Error('Could not retrieve access token.')
}
}
const update = (data) => {
if (data.token) {
this.data.accessTokenData = data
} else {
throw new Error('Invalid response from token api')
}
}
if (this.props.balanceEndpoint !== null) {
return fetch(this.props.accessTokenEndpoint, {
method: 'get',
credentials: 'include'
})
.then(parseResponse)
.then(update)
.catch((err) => Promise.reject(err))
}
}
componentDidMount () {
this.updateToken()
.then(() => this.updateBalance())
}
}
// Test
it('updates the balance', () => {
subject = mount(<Balance {...props} />)
expect(fetchMock.called('balance.json')).to.be.true
})
I can't figure out how to test the above using Mocha. The code is does work the method updateBalance is called and the fetch api call actually does happen, but the test still fails. If I call updateBalance() synchronously it passes... How do I tell the test to wait for the promise to resolve?
You don't really say what you want to test that the
method does, but if all you want to test is that the method resolves on a network call, then there is no need for Sinon or any of that, as this is all you need:
describe("BalanceComponent", () => {
it("should resolve the promise on a successful network call", () => {
const component = new BalanceComponent({any: 'props', foo: 'bar'});
// assumes you call a network service that returns a
// successful response of course ...
return component.updateToken();
});
});
This will test that the method actually works, but it is slow and is not a true unit test, as it relies on the network being there and that you run the tests in a browser that can supply you with a working implementation of fetch. It will fail as soon as you run it in Node or if the service is down.
If you want to test that the method actually does something specific, then you would need to to that in a function passed to then in your test:
it("should change the token on a successful network call", () => {
const component = new BalanceComponent({any: 'props', foo: 'bar'});
const oldToken = component.data.accessTokenData;
return component.updateToken().then( ()=> {
assert(oldToken !== component.data.accessTokenData);
});
});
If you want to learn how to test code like this without being reliant on there being a functioning link to the networked service you are calling, you can check out the three different techniques described in this answer.

Resources