I am sorry if it is simple question, I'm new to javascript
So I have simple axios GET request. It is used three times in my code, so I thought that I could make it an external function, to avoid duplicating code, to making it cleaner and easy readable
The problem is when I call to that function, return value is undefined. And this is because code is working like synchronous. So I thought that I need to make the function return a Promise, and in function call I have to use async/await or then syntax to get the response in the right time. But after many tries code is still running as synchronous
I read a lot of theory on promises and how they work, got a solid understanding of when they change states, but something goes wrong when I try to implement them on my own
Here is the function that retrieves data
const getMessagesFromChat = async (JWT_header, chatId) => {
if (JWT_header !== '') {
//1 let messages
//4 return
axios.get(`${SECURED_API_PATH}/messages/chat/${chatId}`, {
headers: {authorization: JWT_header},
params: {size: 80, page: 0}
})
.then(response => {
console.log('messages (fetch)', response.data)
//1 messages = response.data
//1 return messages
return response.data //2
//3 return Promise.resolve(response.data)
})
.catch(error => {
console.log(error, error.response)
})
//5 return Promise.resolve(messages)
}
}
I marked comments with numbers based on what I've tried
make a variable and return it in then block
make function async so everything it returns is wrapped in promise
return explicit promise using Promise.resolve() in then block
return the whole request as a promise
return explicit promise using Promise.resolve() after the request
All responses except 4 were undefined. In 4 variant log statement shows the promise object itself instead of promise value. In other variants log statement shows first `undefined` response and then the actual response from request
I tried to implement second advice from
https://stackoverflow.com/questions/45620694/how-to-return-response-of-axios-in-return/45622080
but it did not work.
This is the function I tried to use to get the result (onClick)##
const selectChat = () => {
const JWT_header = getToken()
if (JWT_header !== null) {
try {
const messages = await getMessagesFromChat(JWT_header, chatId)
console.log('messages (after fetch)', messages)
//setMessages(messages)
} catch (error) {
console.log(error)
}
} else {
setIsLoggedIn(false)
}
or
const selectChat = () => {
const JWT_header = getToken()
if (JWT_header !== null) {
getMessagesFromChat(JWT_header, chatId)
.then(response => {
console.log(response)
setMessages(response)
})
.catch (error =>console.log(error))
} else {
setIsLoggedIn(false)
}
But none of them worked as expected
What am I doing wrong?
Your function doesn't return anything. A return statement in a then callback is not sufficient, you'd still have to return the promise chain itself from the outer function.
Use either .then() syntax:
function getMessagesFromChat(JWT_header, chatId) { // no `async` here
if (JWT_header !== '') {
return axios.get(`${SECURED_API_PATH}/messages/chat/${chatId}`, {
// ^^^^^^
headers: {authorization: JWT_header},
params: {size: 80, page: 0}
}).then(response => {
console.log('messages (fetch)', response.data)
return response.data
})
} else {
return Promise.resolve(undefined)
// ^^^^^^ important for chaining
}
}
or async/await syntax:
async function getMessagesFromChat(JWT_header, chatId) {
if (JWT_header !== '') {
const respnse = await axios.get(`${SECURED_API_PATH}/messages/chat/${chatId}`, {
// ^^^^^
headers: {authorization: JWT_header},
params: {size: 80, page: 0}
});
console.log('messages (fetch)', response.data)
return response.data
}
// else return undefined
}
Related
I have an HTTP API that returns JSON data both on success and on failure.
An example failure would look like this:
~ ◆ http get http://localhost:5000/api/isbn/2266202022
HTTP/1.1 400 BAD REQUEST
Content-Length: 171
Content-Type: application/json
Server: TornadoServer/4.0
{
"message": "There was an issue with at least some of the supplied values.",
"payload": {
"isbn": "Could not find match for ISBN."
},
"type": "validation"
}
What I want to achieve in my JavaScript code is something like this:
fetch(url)
.then((resp) => {
if (resp.status >= 200 && resp.status < 300) {
return resp.json();
} else {
// This does not work, since the Promise returned by `json()` is never fulfilled
return Promise.reject(resp.json());
}
})
.catch((error) => {
// Do something with the error object
}
// This does not work, since the Promise returned by `json()` is never fulfilled
return Promise.reject(resp.json());
Well, the resp.json promise will be fulfilled, only Promise.reject doesn't wait for it and immediately rejects with a promise.
I'll assume that you rather want to do the following:
fetch(url).then((resp) => {
let json = resp.json(); // there's always a body
if (resp.status >= 200 && resp.status < 300) {
return json;
} else {
return json.then(Promise.reject.bind(Promise));
}
})
(or, written explicitly)
return json.then(err => {throw err;});
Here's a somewhat cleaner approach that relies on response.ok and makes use of the underlying JSON data instead of the Promise returned by .json().
function myFetchWrapper(url) {
return fetch(url).then(response => {
return response.json().then(json => {
return response.ok ? json : Promise.reject(json);
});
});
}
// This should trigger the .then() with the JSON response,
// since the response is an HTTP 200.
myFetchWrapper('http://api.openweathermap.org/data/2.5/weather?q=Brooklyn,NY').then(console.log.bind(console));
// This should trigger the .catch() with the JSON response,
// since the response is an HTTP 400.
myFetchWrapper('https://content.googleapis.com/youtube/v3/search').catch(console.warn.bind(console));
The solution above from Jeff Posnick is my favourite way of doing it, but the nesting is pretty ugly.
With the newer async/await syntax we can do it in a more synchronous looking way, without the ugly nesting that can quickly become confusing.
async function myFetchWrapper(url) {
const response = await fetch(url);
const json = await response.json();
return response.ok ? json : Promise.reject(json);
}
This works because, an async function always returns a promise and once we have the JSON we can then decide how to return it based on the response status (using response.ok).
You would error handle the same way as you would in Jeff's answer, however you could also use try/catch, an error handling higher order function, or with some modification to prevent the promise rejecting you can use my favourite technique that ensures error handling is enforced as part of the developer experience.
const url = 'http://api.openweathermap.org/data/2.5/weather?q=Brooklyn,NY'
// Example with Promises
myFetchWrapper(url)
.then((res) => ...)
.catch((err) => ...);
// Example with try/catch (presuming wrapped in an async function)
try {
const data = await myFetchWrapper(url);
...
} catch (err) {
throw new Error(err.message);
}
Also worth reading MDN - Checking that the fetch was successful for why we have to do this, essentially a fetch request only rejects with network errors, getting a 404 is not a network error.
I found my solution at MDN:
function fetchAndDecode(url) {
return fetch(url).then(response => {
if(!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return response.blob();
}
})
}
let coffee = fetchAndDecode('coffee.jpg');
let tea = fetchAndDecode('tea.jpg');
Promise.any([coffee, tea]).then(value => {
let objectURL = URL.createObjectURL(value);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log(e.message);
});
Maybe this option can be valid
new Promise((resolve, reject) => {
fetch(url)
.then(async (response) => {
const data = await response.json();
return { statusCode: response.status, body: data };
})
.then((response) => {
if (response.statusCode >= 200 && response.statusCode < 300) {
resolve(response.body);
} else {
reject(response.body);
}
})
});
I have a function that posts data via an API. I want to return the response...I am following tutorials online to try to understand async/await, and the way I have it laid out now seems to be correct to me.
I can print inside the postAsset.then statement, but the return value is always undefined. That's what I thought await fixed, unsure why it is still undefined in this case?
const postAsset = async (assetToPost) => {
let token = await retrieveToken()
if (token !== undefined && token !== 'Error') {
const config = {
headers: { Authorization: `Bearer ${token}` }
};
let urlAssets = `${API_ROUTE}${ASSETS.ASSETS_API_URL}`
axios
.post(urlAssets, assetToPost, config)
.then(function (response) {
let assetId = response.data
return assetId
})
.catch(function (error) {
console.log('POSTING ASSET ERROR: ', error.message);
return -1
})
}
}
const postData = async () => {
// post asset, get id response.
// then post beacons in loop, get id response for each
// then post a beaconId as part of assetId
let assetId = await postAsset(asset)
console.log(assetId)
}
Because you are using .then and not await - which would work if you'd return the promise that you get, but you don't.
Solution 1:
Return the promise you get from axios.post (add return in front of it):
return axios
.post(...)
.then(...)
Solution 2 (better):
Don't mix .then with async/await, instead only use async/await:
try {
const response = await axios.post(urlAssets, assetToPost, config)
return response.assetId
} catch (error) {
console.log('POSTING ASSET ERROR:', error)
return -1
}
(By the way, I changed error.message to just error so you won't lose the important stack information!)
the function postAsset should return a promise, and within the promise should execute the call axios.post
You're returning your assetId from the axios call but not returning anything from the postAsset method.
Just add the return keyword before you axios call. It will work.
return axios
.post(urlAssets, assetToPost, config)
.then(function (response) {
let assetId = response.data
return assetId
})
.catch(function (error) {
console.log('POSTING ASSET ERROR: ', error.message);
return -1
})
I have this piece of code that calls a function getTableData and expects a Promise in return.
function populateTableRows(url) {
successCallback = () => { ... };
errorCallback = () => { ... };
getTableData(url, successCallback, errorCallback).then(tableData => {
// do stuff with tableData
}
}
This is used in many places across my codebase, and I'm looking to keep the behavior the same as I move away from using jQuery's ajax (and jQuery in general)
In getTableData, I'm currently using $.ajax like so
function getTableData(url, successCallback, errorCallback) {
successCallback = successCallback || function() {};
errorCallback = errorCallback || function() {};
const ajaxOptions = {
type: 'POST',
url: url,
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true,
data: { // some data }
};
return $.ajax(ajaxOptions).done(successCallback).fail(errorCallback);
}
This currently returns a Promise for successful requests. For bad requests where fail is invoked, it doesn't appear that a Promise is returned and the then doesn't run in the calling function (which is okay in this case).
When converting the request over to use fetch, I have something like this
function getTableData(url, successCallback, errorCallback) {
successCallback = successCallback || function() {};
errorCallback = errorCallback || function() {};
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
credentials: 'include',
body: { // some data }
})
.then(response => {
let json = response.json();
if (response.status >= 200 && response.status < 300) {
successCallback(json);
return json;
} else {
return json.then(error => {throw error;});
}
}).catch((error) => {
errorCallback(error);
return
});
Successful requests appear to be behaving similarly to the ajax code that I currently have, but now the then callback is running for bad requests which is causing errors in my code.
Is there a way with fetch to mimic the fail behavior of jQuery where the Promise is seemingly aborted for bad requests? I'm fairly new to using Promises and after some experimentation/searching I haven't been able to come up with a solution.
When you .catch() in a chain of promises, it means you already handled the error, and subsequent .then() calls continue successfully.
For example:
apiCall()
.catch((error) => {
console.log(error);
return true; // error handled, returning true here means the promise chain can continue
})
.then(() => {
console.log('still executing if the API call fails');
});
What you want, in your case, is when you handle the error with the callback, to continue to throw it so the promise chain is broken. The chain then further needs a new .catch() block to handle the new error.
apiCall()
.catch((error) => {
console.log(error); // "handled", but we're still not done
throw error; // instead of returning true, we throw the error further
// 👆 this can also be written as `return Promise.reject(error);`
})
.then(() => {
console.log('not executing anymore if the API call fails');
})
.catch((error) => {
// handle the same error we have thrown from the previous catch block
return true; // not throwing anymore, so error is handled
})
.then(() => {
console.log('always executing, since we returned true in the last catch block');
});
By the way, what you return from one then/catch block, the following one will get it as a param.
apiCall()
.then((response) => {
/* do something with response */;
return 1;
})
.catch((error) => { return 'a'; })
.then((x) => console.log(x)) // x is 'a' if there's an error in the API call, or `1` otherwise
In your .catch you implicitly return undefined and thus "handle" the error. The result is a new Promise that fulfills to undefined.
.catch((error) => {
errorCallback(error);
return Promise.reject();
});
should be enough to keep the returned Promise rejecting.
Or you assign the intermediate Promise to a var and return that, and not the result to the fail handling:
var reqPromise = fetch(url, {
// ...
})
.then(response => {
// ...
return json.then(error => {throw error;});
});
reqPromise.catch((error) => {
errorCallback(error);
return
});
return reqPromise;
I am having a Lambda function, in which I am taking a parameter from Query String and using it to call a downstream service that will inturn return a promise. I am unable to send back the response received by the Promise.
This is my Lambda function:
export const handler = async (event) => {
let sessionId = event.queryStringParameters.sessionId;
const testClient = new Client.WebStoreClient(config);
const sessionResponse = getSession(testClient, sessionId);
sessionResponse.then(function (result) {
console.log(result);
});
const response = {
statusCode: 200,
body: JSON.stringify(sessionResponse),
};
return response;
};
async function getSession(testClient, sessionId) {
let data = await testClient.getSession(sessionId, headers)
.then((response) => response.body);
return data;
}
When I am executing this, I am getting a null response: { statusCode: 200, body: '{}' } from the Lambda function output. I tried to console log the value that I have gotten and was receiving this: Promise { <pending> }
I am suspecting this to be a Javascript/Typescript Promise issue which I am slightly neglecting. Any help is appreciated here. Thanks in advance.
In your response, you are trying to stringify the promise instead of the promise value and it gives you an empty object as a String.
You can try it:
JSON.stringify(Promise.resolve(1)) // output is "{}"
You can only access the value of a Promise from inside the callback you pass to then.
Think of it this way: inside of then is the future, the surrounding code is the present. You can't reference future data in the present.
You probably wanted to write
return sessionResponse.then(function (result) {
return {
statusCode: 200,
body: JSON.stringify(result),
}
});
The above returns a promise containing your response.
You could have also just written getSession this way
function getSession(testClient, sessionId) {
return testClient.getSession(sessionId, headers)
.then((response) => response.body);
}
I suggest that you practice using promises without async/await
An alternative to geoffrey's answer, note that both of your functions are async, meaning you can use the same await keyword you used in getSession().
export const handler = async (event) => {
// ...
const sessionResponse = await getSession(testClient, sessionId);
// ^
console.log(sessionResponse);
const response = {
statusCode: 200,
body: JSON.stringify(sessionResponse),
};
return response;
};
async function getSession(testClient, sessionId) {
let data = await testClient.getSession(sessionId, headers)
.then((response) => response.body);
return data;
}
This is roughly the equivalent of...
export const handler = (event) => {
// ...
return getSession(testClient, sessionId).then((data) => {
console.log(data);
const response = {
statusCode: 200,
body: JSON.stringify(sessionResponse),
};
return response;
});
};
function getSession(testClient, sessionId) {
return testClient
.getSession(sessionId, headers)
.then((response) => response.body);
}
Note that your getSession() function is returning a Promise by virtue of async (and in the translated example above). Your handler() function is likewise returning a Promise, so whatever is calling it will need to use a .then() or be async itself to leverage an await. As you can see this asynchronous requirement cascades.
I´m pretty new to Promises and found many examples here how to access the actual value which is always done with console.log. But my goal is to store the result in a variable and work with it.
getdata = () =>
fetch(
"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&outputsize=full&apikey=demo"
)
.then(response => {
if (response.status === 200) {
return response.json();
} else {
throw new Error("This is an error");
}
})
.then(data => {
console.log(data);
});
getdata();
This code works. Can you help me to rewrite it that the getdata() function allows me to store the result in a variable. Return does not work since I will receive another pending Promise.
You can do it like this:
getdata = () =>
fetch(
"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&outputsize=full&apikey=demo"
).then(response => {
if (response.status === 200) {
return response.json();
} else {
throw new Error("This is an error");
}
});
getdata().then(data => {
//I can do whatever with data
});
Of course you would also want to handle the scenario where the request failed, so you could also chain a .catch(). Alternately, if you have your build process configured for it, you can use async and await so you could do:
try {
const data = await getdata();
} catch(err) {
}
This would need to be in a function marked as async
Well at first we need to declare a variable let's say temp. Then use fetch API to request our query with URL. If server status is 200 then it will return a promise, we need to use then method by passing any argument (res, response, r anything...) and then a fat arrow function (=>) so that we can make the response as json format. After then we need to use another then method to return the json output and assign the value to our declared temp variable.
But if there is any error like 500, 400, 404 server error we need to use catch method with err argument and console it out.
let temp;
fetch('https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&outputsize=full&apikey=demo')
.then(res => res.json())
.then(data => temp = data)
.catch(err => console.log(err));