It gets complicated to me when I mix the promise with subscribe and another async task together.
This is my auth service:
getCurrentUserToken(){
return new Promise((resolve,reject)=>{
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
resolve(idToken)
}).catch(function(error) {
reject(error)
});
})
}
This is my HTTP service:
sendEmail(email) {
return this.authService.getCurrentUserToken().then(token => {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic server-Password',
})
};
let data = email
data['idToken'] = token
return this.http.post(this.apiServer + 'sendEmail', data, httpOptions)
})
}
This is how I call the sendEmail(email) function at the component:
Observable.fromPromise(this.httpService.sendEmail(element)).subscribe(
data3 => {
console.log(data3)
}, error => {
console.log(error)
}
))
I have to pass currentUserToken to the API to let the API authenticate the user session. Still, both of the the getCurrentUserToken() sendEmail() are running in async, so I have to use Promise to pass the Token to sendEmail() function, and let the sendEmail function to call the API to send the email.
Without the promise, I am able to subscribe to the http.post like this:
this.httpService.sendEmail(element).subscribe(
data3 => {
console.log(data3)
}, error => {
console.log(error)
}
))
Unfortunately, I screwed it up when I added the promise into it, and the console.log is returning this:
Observable {_isScalar: false, source: Observable, operator: MapOperator}
Please advise on how to subscribe to the http.post that is placed inside the Promise.
There's seriously no need of Complicating things here.
I'll use async/await syntax here and for that, we'll have to work with Promises instead of Observables. Good thing is, we can leverage the toPromise() method on an Observable value to change it to a Promise
Focus on my comments in the code as well
Here's the implementation
For getCurrentUserToken
getCurrentUserToken() {
return firebase.auth().currentUser.getIdToken(true);
// This will already return a Promise<string>
// So no need to do a .then and then return from there.
}
For sendEmail
async sendEmail(email) {
// Since getCurrentUserToken returns a Promise<string> we can await it
const token = await this.authService.getCurrentUserToken();
// token will now have the Current User Token
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic server-Password',
})
};
let data = email
data['idToken'] = token
return this.http.post(this.apiServer + 'sendEmail', data, httpOptions).toPromise();
// Notice how we're calling the .toPromise() method here
// to change Observable into a Promise
}
How to use it?
This code will go in your Component Method where you were previously calling this.httpService.sendEmail. DO MAKE SURE TO MARK THAT FUNCTION AS async THOUGH.
// We can only await something in a function which is declared of type async
async sendEmail() {
try {
const data = await this.httpService.sendEmail(element);
// Since sendEmail again returns a Promise, I can await it.
console.log(data);
} catch (error) {
console.log(error);
}
}
Why don't we use Observable instead of Promises here.
getCurrentUserToken() {
return new Observable(obs => {
firebase
.auth()
.currentUser.getIdToken(/* forceRefresh */ true)
.then(function(idToken) {
obs.next(idToken);
obs.complete();
})
.catch(function(error) {
obs.error(error);
});
});
}
sendEmail(email): Observable {
return new Observable(obs => {
this.authService.getCurrentUserToken().subscribe(token => {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
Authorization: 'Basic server-Password'
})
};
let data = email;
data['idToken'] = token;
this.http
.post(this.apiServer + 'sendEmail', data, httpOptions)
.subscribe(
result => {
obs.next(result);
obs.complete();
},
error => {
obs.error();
}
);
});
});
}
// now call the service from Component like this.
this.httpService.sendEmail(element).subscribe(
data3 => {
console.log(data3)
}, error => {
console.log(error)
}
));
Related
When I submit my form, I am not returning any data, not even in my console. I am trying to return details from WHOIS regarding the URL that is searched, and am getting nothing back.
Can anyone provide any advice as to why this might be the case?
Here is my front end script tag, after my form:
document.getElementById('search').addEventListener('submit', function(e) { e.preventDefault(); getDetails(); })
async function getDetails(url = `http://localhost:3000/lookup/${url}`, data = {}) {
return new Promise(function (resolve, reject) {
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'text/html'
},
body: JSON.stringify(data)
}).then(async response => {
if (response.ok) {
response.json().then(json => resolve(json))
console.log(data);
} else {
response.json().then(json => reject(json))
}
}).catch(async error => {
reject(error)
})
})
}
On my express backend I am using req.params.url if that helps provide any context at all...
My Status Code is 200, and all appears to be normal in the Headers tab...
You have a mix of promise and async syntax, which is confusing, let's translate it first by unpicking the promise and then into await (if you can use async then do, it's easer than Promise/then):
document.getElementById('search').addEventListener(
'submit',
function(e) {
e.preventDefault();
getDetails();
});
async function getDetails(url = `http://localhost:3000/lookup/${url}`, data = {})
{
// Fetch will throw an exception if it can't connect to the service
const response = await fetch(url, {
method: 'GET',
headers: { 'Content-Type': 'text/html' },
body: JSON.stringify(data)
});
if (response.ok)
return await response.json();
// We could connect but got an error back from the service
// There may not be a response body, so response.json() or .body() might crash
throw new Error(`Error from server ${response.status}: ${response.statusText}`;
// We don't need catch, as any exception in an await will cascade up anyway
}
This makes it much more readable, and it's apparent that getDetails doesn't make any changes itself, it just returns the JSON from the service. The fix needs to be in the event listener - it needs to do something with that result:
document.getElementById('search').addEventListener(
'submit',
async e => {
e.preventDefault();
const searchResult = await getDetails();
// Do something to show the results, populate #results
const resultsElement = document.getElementById('results');
resultsElement.innerText = JSON.stringify(searchResult);
});
You are mis-using async/await. Try this:
async function getDetails(url = `http://localhost:3000/lookup/${url}`, data = {}) {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'text/html'
},
body: JSON.stringify(data)
});
const json = await response.json();
if (response.ok) {
console.log(json);
return json;
} else {
throw new Error(json);
}
}
In essence await is a replacement for then (but you can only use it in functions marked with async).
I'm working on load testing my API, but at some point I make a call to a different API.
Since I don't want to stress the second one, whenever I'm load testing I want to set a timeout and return an OK response like this:
function sendMessage(requestLib, blockApi, logger) {
if(!blockApi){
return (*my params*) => requestLib(`someURL`, {
headers: { Authorization: `Bearer ${token}` },
method: 'post',
data
});
}else{
logger.info("About to use the promise");
const response = returnOk.then(function() {
return new Response(200, {}, null, 'dummy.com');
});
return response;
}
}
returnOk is a Promise I defined earlier this way:
const returnOk = new Promise((resolve, reject) => {
setTimeout( function() {
resolve("Success!")
}, 2000)
});
And the function sendMessage is called inside a different function like this:
module.exports = ({ requestLib, Logger }) => async function(req, res) {
// Some unrelated code to decide if I'll call sendMessage
const response = await sendMessage(requestLib, blockApi, logger)(params);
// I log the response
res.end();
}
The regular flow works like a charm, but when I'm load testing and I get to returnOk.then()...
It throws
sendMessage(...) is not a function
If I remove the timeout and just return
return new Response(200, {}, null, 'dummy.com');
Things work just fine.
For some reason, your sendMessage(…) function returns another function:
return (*my params*) => …
You will need to do the same when mocking sendMessage:
function sendMessage(requestLib, blockApi, logger) {
if (!blockApi) {
return (*my params*) => requestLib(`someURL`, {
headers: { Authorization: `Bearer ${token}` },
method: 'post',
data
});
} else {
return (*my params*) => {
const returnOk = new Promise((resolve, reject) => {
setTimeout(resolve, 2000)
});
logger.info("About to use the promise");
return returnOk.then(function() {
return new Response(200, {}, null, 'dummy.com');
});
};
}
}
Btw, you really shouldn't have this boolean blockApi parameter to sendMessage. Write two distinct functions with the same signature, and use dependency injection in the code that is calling it.
Hi I have following function returning promise
module.exports.getJwtToken = async() => {
const httpSearchAddressUXConfig = {
headers: {
Accept: 'application/json',
mock: false,
'Content-Type': 'application/json',
},
data: reqBody,
method: 'POST',
url: `${config.app.entTokens.host}`, // need to get from env variables
timeout: config.app.enterpriseHTTPTimeout
};
try {
const token = await axios(httpSearchAddressUXConfig);
return token.data;
} catch (err) {
throw err;
}
I have following test case which fails with unhandled Promise rejection error
it('should find Jwt token ', async(done) => {
const actualTokenfound = jwtTokenService.getJwtToken();
return actualTokenfound
.then(result => expect(result).toBe(Object))
.then(done);
});
Any Suggestions ?
If you define a async function, you don't need to use "done". I guess something like this it'll works.
it('should find Jwt token ', async () => {
const actualTokenfound = await jwtTokenService.getJwtToken();
expect(result).toBe(Object));
});
I'm having some problems getting a response from a chained Promise.
I have my component where the chain starts
component
componentDidMount = async ()=> {
try{
const products = await post('payments/getProducts', {});
console.log(products);
} catch(e) {
console.log(e)
}
}
This component calls my API helper:
async function post(url, data) {
token = null;
if (firebase.auth().currentUser) {
token = await firebase.auth().currentUser.getIdToken();
}
try {
const response = axios({
method: 'POST',
headers: {
Authorization: `${token}`,
},
data,
url: `${API_URL}${url}`,
})
return response;
} catch(e){
Promise.reject(e);
}
}
and my API Helper then calls a Firebase Cloud Function which calls Stripe:
paymentRouter.post('/getProducts', (req, res) => {
return stripe.products.list()
.then(products => {
console.log(products.data)
return products.data;
})
.catch(e => {
throw new functions.https.HttpsError('unknown', err.message, e);
})
})
Calling the function is no problem, and my Cloud Function logs out the product data, but I can't get the response to log in my API Helper nor my component.
Promise.reject(e);
That is completely senseless as it creates a new rejected promise that is not used anywhere. You could await it so that it gets chained into the promise returned by the async function, or you just return the promise from axios:
async function post(url, data) {
let token = null; // always declare variables!
if (firebase.auth().currentUser) {
token = await firebase.auth().currentUser.getIdToken();
}
return axios({
method: 'POST',
headers: {
Authorization: `${token}`,
},
data,
url: `${API_URL}${url}`,
});
}
Now the errors don't go into nowhere anymore and you can probably debug the problem :)
So I'm using React with React-Router.
I have a onEnter hook which checks if the user is authenticates yes/no and executes the desired action.
export function requireAuth(nextState, replaceState) {
if (!isAuthenticated()) {
if (!Storage.get('token')) replaceState(null, '/login');
return delegate().then(() => replaceState(null, nextState.location.pathname));
}
if (nextState.location.pathname !== nextState.location.pathname) {
return replaceState(null, nextState.location.pathname);
}
}
When the token is expired I call a delegate function which looks like:
export function delegate() {
const refreshToken = Storage.getJSON('token', 'refresh_token');
return getData(endpoint)
.then(res => {
Storage.set('token', JSON.stringify({
access_token: res.data.access_token,
refresh_token: refreshToken,
}));
});
}
The delegate function indeed refresh the tokens in the localStorage. But the requests after the replaceState are not using the updated token, but the previous one. I think this is a async issue, someone knows more about this?
Edit: The function where I use the token:
function callApi(method, endpoint, data) {
return new Promise((resolve, reject) => {
let headers = {
'Accept': 'application/json',
'X-API-Token': Storage.getJSON('token', 'access_token'),
};
const body = stringifyIfNeeded(data);
const options = { method, headers, body };
return fetch(endpoint, options)
.then(response =>
response.json().then(json => ({ json, response }))
).then(({ json, response }) => {
if (!response.ok) {
reject({ json, response });
}
resolve(json);
}).catch((error, response) => {
reject({ error, response });
});
});
}