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 :)
Related
I'm trying to force Node to wait for either a success or a failure. I understood fetch to return a promise and I thought I told it how to handle both.
The following code does not honor the await I asked it to do:
async function getAccessToken() {
...
let fetchResult = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers: headers
}).then(success => {
console.log("Success reached. " + JSON.stringify(success));
process.exit(2);
}, other => {
console.log("Other reached. " + JSON.stringify(other));
process.exit(3);
});
console.log('## after fetch fetchResult=' + fetchResult);
...
}
You might think that the await would cause it to, wait for the Promise to complete, but instead it leaves the whole function, and goes back to the caller. It does not print the '## after fetch fetchResult=' line. Neither the failure, nor success handler is executed.
I should point out that it also does not appear to make the requested POST call either. Instead, it sees that request and does something completely different without raising any exception.
Why is it not honoring the 'await' keyword whatsoever?
--- If I try the try/catch approach as follows:
async function getAccessToken() {
console.log('##getAccessToken BP1');
if (argumentParserResult.authenticationScheme == 'OAUTH2') {
console.log('##getAccessToken BP2');
const fetch = require('node-fetch');
const url = argumentParserResult.resourceUrl;
console.log('##getAccessToken BP3');
let formData = new URLSearchParams({
'grant_type': 'client_credentials',
'client_id': argumentParserResult.clientId,
'scope': argumentParserResult.clientScope,
'client_secret': argumentParserResult.clientSecret
})
console.log('##getAccessToken BP4');
let headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
console.log('##getAccessToken BP5');
console.log('POST ' + argumentParserResult.authorizationUrl);
console.log(JSON.stringify(formData));
console.log('##getAccessToken BP6');
try {
console.log('##getAccessToken BP7');
const response = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers,
});
console.log('##getAccessToken BP8');
console.log(`Success reached.`, JSON.stringify(response));
const json = await response.json();
console.log('##getAccessToken BP9');
console.log(`Other reached.`, json);
return json;
} catch (error) {
console.log('##getAccessToken BP10');
console.log(`!! something went wrong`, error.message);
console.error(error);
return error;
} finally {
console.log('##getAccessToken BP11');
console.log(`fetch finished`);
}
console.log('##getAccessToken BP12');
}
console.log('##getAccessToken BP13');
return "Should not have reached this point";
}
I get
##getAccessToken BP1
##getAccessToken BP2
##getAccessToken BP3
##getAccessToken BP4
##getAccessToken BP5
POST https://some-url
{}
##getAccessToken BP6
##getAccessToken BP7
As you can see, it goes just inside of the try block, then goes back to the caller without triggering the finally, error handlers or the logging after the fetch.
Using the .then approach as follows:
async function getAccessToken() {
console.log('##getAccessToken BP1');
if (argumentParserResult.authenticationScheme == 'OAUTH2') {
console.log('##getAccessToken BP2');
const fetch = require('node-fetch');
const url = argumentParserResult.resourceUrl;
console.log('##BP1.9');
let formData = new URLSearchParams({
'grant_type': 'client_credentials',
'client_id': argumentParserResult.clientId,
'scope': argumentParserResult.clientScope,
'client_secret': argumentParserResult.clientSecret
})
console.log('##getAccessToken BP3');
let headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
console.log('##getAccessToken BP4');
console.log('POST ' + argumentParserResult.authorizationUrl);
console.log(JSON.stringify(formData));
let response = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers: headers
}).then(success => {
console.log('##getAccessToken BP5');
console.log("Success reached. " + JSON.stringify(success));
return success // !--> LOOK HERE, you should return the success variable
}).catch(e => {
console.log('##getAccessToken BP6');
console.log(e) // !--> LOOK HERE, if you catch the error, no error will be thrown to the caller
return e
});
console.log('##getAccessToken BP7');
console.log('## after fetch fetchResult=', fetchResult); // !--> LOOK HERE, this log will always log something now, it could be the responso or the error
}
console.log('##getAccessToken BP8');
}
I get these logs:
##getAccessToken BP1
##getAccessToken BP2
##BP1.9
##getAccessToken BP3
##getAccessToken BP4
POST https://login.microsoftonline.com/5a9bb941-ba53-48d3-b086-2927fea7bf01/oauth2/v2.0/token
{}
As you can see above, it goes just to the point of the fetch, then returns to the calling function.
In neither case, can I see any evidence that the fetch was ever called.
Try this:
async function getAccessToken() {
try {
const response = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers,
});
console.log(`Success reached.`, JSON.stringify(response));
const json = await response.json();
console.log(`Other reached.`, json);
} catch (error) {
console.log(`!! something went wrong`, error.message);
console.error(error);
} finally {
console.log(`fetch finished`);
}
}
You don't need to use thenable object when writing with async/await, instead, catch the error with a try catch bloc, and just get the async value using return of awaited function.
You are mixing await and then. It is not forbidden, but in most simple case you don't need it.
Solution without then:
async function getAccessToken() {
try {
console.log('fetching data') // this log will always appear as first log, before fetching data
let fetchResult = await fetch(argumentParserResult.authorizationUrl,
{
method: 'POST',
body: formData,
headers: headers
})
let jsonR = await fetchResult.json()
console.log('fetch done') // this log will appear only if fetch is done with no errors
} catch (e) {
console.error('something went wrong', e) // this log will appear only if there was an error
}
console.log('after all') // this log will appear always, after fetch (even if fetch fails or not)
}
Solution with then:
async function getAccessToken() {
let fetchResult = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers: headers
}).then(success => {
console.log("Success reached. " + JSON.stringify(success));
return success // !--> LOOK HERE, you should return the success variable
}).catch(e => {
console.log(e) // !--> LOOK HERE, if you catch the error, no error will be thrown to the caller
return e
});
console.log('## after fetch fetchResult=', fetchResult); // !--> LOOK HERE, this log will always log something now, it could be the responso or the error
}
As you can see, error handling is not quite convenient in the second solution. That's why you should not mix await with then, unless you know what you are doing
The point of async/await is to get rid of the callbacks and make the code more procedural. Your code:
async function getAccessToken() {
...
let fetchResult = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers: headers
})
.then( success => {
console.log("Success reached. " + JSON.stringify(success));
process.exit(2);
}, other => {
console.log("Other reached. " + JSON.stringify(other));
process.exit(3);
});
console.log('## after fetch fetchResult=' + fetchResult);
...
}
fails, because you are
Waiting for fetch() to resolve and return a result, and
In your then() chain, you are
Invoking process.exit() in the case of either success or failure.
Than means you kill the entire process as soon as the call to fetch() resolves with either a success or a failure.
If you do something like this:
async function getAccessToken() {
...
const opts = {
method: 'POST',
body: formData,
headers: headers
};
const {json, err} = await execFetch( argumentParserResult.authorizationUrl, opts );
if ( err ) {
console.log("that didn't work!", err);
process.exit(1);
}
...
}
async function execFetch( url, opts ) {
const response = { json: undefined, err: undefined };
const { res, err } = await fetch( argumentParserResult.authorizationUrl, opts )
.then( res => ({ res , err: undefined }) )
.catch( err => ({ res: undefined , err }) );
if ( err ) {
response.err = err;
}
else if ( !res.ok ) {
// non-2xx HTTP status
response.err = new Error(`${res.status}: ${res.statusText}`);
}
else {
// the 2xx happy path: deserialize the JSON response body into a JS object
response.json = res.json();
}
return response;
}
Your call to fetch() will always succeed and hand you back a tuple with a json and an err property.
A successful call will return something like this:
{
json: { a: 1, b: 2, c: 3, },
err: undefined,
}
Whilst a call to fetch() that fails will return something like this:
{
json: undefined ,
err: /* some error object with details about what went south */,
}
currently, my code is returning a promise I need it to return the object that It is getting from the API call, how would do that?
import axios from 'axios';
const baseUrl = 'http://api.openweathermap.org/data/2.5/weather?';
const getWeatherData = async (city,country) => {
// const result=await axios.get(`http://api.openweathermap.org/data/2.5/weather?q=${city},${country}&APPID=180941f68139fba12f166dc35d9b688b`)
// return result;
axios({
method: "GET",
url: `http://api.openweathermap.org/data/2.5/weather?q=${city},${country}&APPID=180941f68139fba12f166dc35d9b688b`,
})
.then((response) => {
return response.data;
})
.catch((error) => {
console.log(error);
});
}
export default getWeatherData;
try {
const response = await axios({
method: "GET",
url: `http://api.openweathermap.org/data/2.5/weather?q=${city},${country}&APPID=180941f68139fba12f166dc35d9b688b`,
});
return response.data;
} catch (err) {
console.error(err);
}
You can rewrite your axios call this way since your function is flagged as async.
async functions always return promises. Within async functions, you can use await infront of other async functions or functions that return promises.
I'm not sure but i cant get the life of me to get this to resolve. its always pending. I'm not the best with promises so please help me out.
export async function getQuotes() {
const options = {
headers: {
"x-rapidapi-host": API_URL,
"x-rapidapi-key": API_KEY,
"content-type": "application/x-www-form-urlencoded",
},
};
let res = await axios
.post(API_URL, {}, options)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
return res;
}
and this is how i'm calling it:
const new_data = dataApi.getQuotes();
console.log(new_data);
with the new_data variable, i'd like to access the data that was returned. I keep getting back pending promises instead.
In javascript, async functions return a promise that resolves to their return value. In order to access the data, you have to wrap it in another async function and call await or use .then:
// Either:
async function main() {
const new_data = await dataApi.getQuotes();
console.log(new_data);
}
// or
dataApi.getQuotes().then(new_data => console.log(new_data));
Read more here.
You can wait until the promise has been fulfilled and then process the data as follows:
dataApi.getQuotes()
.then(function(new_data) {
console.log(new_data);
})
.catch(function (error) {
// error handling
});
You should use async/await or promises (.then and .catch) - but not both at the same time. You should also know that an async function always returns a Promise, so you can use it with the then/catch syntax if you want.
// Call with the then/catch syntax
dataApi.getQuotes()
.then((data) => console.log(data))
.catch((err) => console.error(err))
// Is the same that this call with async await
async function main() {
try {
const data = await dataApi.getQuotes();
console.log(data)
} catch (err) {
console.error(err)
}
}
About your function, you're using async/await here. So you may not use then/catch but await in its body.
export async function getQuotes() {
const options = {
headers: {
"x-rapidapi-host": API_URL,
"x-rapidapi-key": API_KEY,
"content-type": "application/x-www-form-urlencoded",
},
};
try {
let res = await axios.post(API_URL, {}, options);
console.log(response);
catch (error) {
console.log(error);
}
return res;
}
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.
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)
}
));