Reputation: 101
I have 2 files 1. api_service.ts
import { HttpClient } from 'aurelia-http-client';
import { autoinject } from "aurelia-framework";
import { ObserverLocator } from "aurelia-binding";
@autoinject()
export class ApiServices {
private app_url = 'http://example.com/';
private host = 'http://api.example.com:8080';
constructor(private store: Store, private client: HttpClient, private observerLocator: ObserverLocator) { }
GetInstallationId() {
let url = this.host + "/1/api/iid";
let iid;
this.client.get(url)
.catch((err) => { })
.then(res => {
let response = JSON.parse(res.response);
iid = response.iid;
});
return iid;
}
}
here I am trying to get some value from the server and in the HTTP response, I can see that I am getting it.
import { Store } from './localstorage_service';
import { ApiServices } from './api_service';
import { autoinject } from "aurelia-framework";
import { ObserverLocator } from "aurelia-binding";
@autoinject()
export class AppServices {
private installation_ID: string;
constructor(private store: Store, private apiServices: ApiServices, private observerLocator: ObserverLocator) { }
getInstallationID() {
const iid = this.store.getValue('installation_ID');
console.log("iid = " + iid);
if (iid && iid != undefined) {
console.log('inside if iid');
// this.installation_ID = this.apiServices.GetInstallationId();
this.installation_ID = iid;
} else {
console.log('inside else iid');
this.installation_ID = this.apiServices.GetInstallationId();
}
this.store.setValue('installation_ID', this.installation_ID);
console.log("after if condition iid = " + iid);
return this.installation_ID;
}
}
here in this code I'm calling the function GetInstallationId()
from api_services.ts
but it is not waiting for the http request
to complete. Is there a way to make the statement in the next statement execute after the GetInstallationId()
is executed?
Upvotes: 1
Views: 3314
Reputation: 629
The short answer: you need to support asynchronous behavior.
You may do this via (a) Promises and/or (b) async/await. The former is supported in all browsers except IE 11 and there are polyfills like this one if you need to support IE 11. The latter is currently not supported in IE at all and as it requires new JS keywords/syntax must be used with a transpiler like Babel.
This answer will use Promises as they have fewer dependencies than async/await. The snippets below have been updated to use Promises -- look for the comments like:
// REPLACED THIS LINE
... // WITH THIS LINE
and
// ADDED THIS LINE
which identify the updates.
GetInstallationId
is an asynchronous function and must return a promise.
class ApiServices {
...
GetInstallationId() {
let url = this.host + "/1/api/iid";
return new Promise(function(accept, reject) {
let iid;
this.client.get(url)
.catch((err) => { })
.then(res => {
let response = JSON.parse(res.response);
// REPLACED THIS LINE
// iid = response.iid;
// WITH THIS LINE which accepts the promise
accept(response.iid);
})
// ADDED THIS LINE to catch any errors
// e.g. when invalid response received
.catch(reject);
});
}
}
Because getInstallationID
calls GetInstallationId
, it too must return a promise. This example defines a separate function, handler
, which is then bound via .bind(this)
to ensure that whenever the function is executed, this
will always refer back to the correct ApiService object.
class AppServices {
...
getInstallationID() {
function handler(accept, reject) {
const iid = this.store.getValue('installation_ID');
console.log("iid = " + iid);
if (iid && iid != undefined) {
console.log('inside if iid');
// this.installation_ID = this.apiServices.GetInstallationId();
this.installation_ID = iid;
} else {
console.log('inside else iid');
// REPLACED THIS LINE
// this.installation_ID = this.apiServices.GetInstallationId();
// WITH THIS LINE
this.apiServices.GetInstallationId().then(accept,reject);
}
this.store.setValue('installation_ID', this.installation_ID);
console.log("after if condition iid = " + iid);
// REPLACED THIS LINE
// return this.installation_ID;
// WITH THIS LINE
accept(this.installation_ID);
};
return new Promise(handler.bind(this));
}
...
}
The above function is just one example which will work cross browser. Another solution which requires ES6 support and/or transpilation is to use arrow functions:
//
// Arrow function version
//
class AppServices {
...
getInstallationID() {
// NOTE the => which is shorthand for `(function (accept,reject) {...}).bind(this)`
return new Promise((accept, reject) => {
const iid = this.store.getValue('installation_ID');
console.log("iid = " + iid);
if (iid && iid != undefined) {
console.log('inside if iid');
// this.installation_ID = this.apiServices.GetInstallationId();
this.installation_ID = iid;
} else {
console.log('inside else iid');
// REPLACED THIS LINE
// this.installation_ID = this.apiServices.GetInstallationId();
// WITH THIS LINE
this.apiServices.GetInstallationId().then(accept,reject);
}
this.store.setValue('installation_ID', this.installation_ID);
console.log("after if condition iid = " + iid);
// REPLACED THIS LINE
// return this.installation_ID;
// WITH THIS LINE
accept(this.installation_ID);
});
}
...
}
With the above refactoring, you can now call getInstallationID()
from e.g. another view model like this:
import {AppServices} from '...';
import {autoinject} from 'aurelia-framework';
@autoinject()
export class SomeViewModel {
constructor(appServices) {
this.appServices = appServices;
}
callGetInstallationId() {
this.appServices.getInstallationID().then(function(id) {
// the ID that is passed here is the one that is `accept`ed.
// ie. if AppServices.getInstallationID calls accept('123'), then
// the '123' will be passed to this function as the first argument
}, function(e) {
// this function will be called if AppServices.getInstallationID
// calls reject. e will be the Error object, if any, that is passed
// to the reject method.
});
}
}
Upvotes: 7