How to use await and async during axios API calls - javascript

const axios = require('axios');
const getShipmentDetails = ((nextCall) => {
const res = axios({
method: 'post',
url: nextCall,
headers:{ Authorization: "<Generated Bearer Token>"},
data:{
"filter" : {
"type" : "postDispatch",
"states" : ["SHIPPED"],
"orderDate" : {
"from" : "2022-04-01",
"to" : "2022-04-04"
}
}
}
})
if(res['data']['hasMore'] == false ){
return res['data']['shipments'].concat(getShipmentDetails(res['data']['nextPageUrl']))
}
else{return res['data']['shipments']}
});
const result = getShipmentDetails("https://api.flipkart.net/sellers/v3/shipments/filter/");
console.log(result);
I am Recursively fetching data from paginated API. I am getting multiple errors while adding await/async functions in this code. Due to delay from the API calls output is printing "Undefined". Please update this code with await/async or any suitable method so that data from all the pages is concatenated.

const axios = require('axios')
const getShipmentDetails = async ((url) => {
const res = await axios({
method: 'post',
url: url,
headers: { Authorization: "<Generated Bearer Token>"},
data: {
"filter" : {
"type" : "postDispatch",
"states" : ["SHIPPED"],
"orderDate" : {
"from" : "2022-04-01",
"to" : "2022-04-04"
}
}
}
})
if (res['data']['hasMore'] == true) {
const more = await getShipmentDetails(res['data']['nextPageUrl'])
return res['data']['shipments'].concat(more);
}
else {
return res['data']['shipments']
}
})
getShipmentDetails("https://api.flipkart.net/sellers/v3/shipments/filter/")
.then(result => console.log(result))
Should do what you want. I made the assumption that you got your hasMore conditional backwards since logically, you'd expect hasMore to be true if there were additional pages.
A note about async/await and promises:
If you use promises you have to use promises all the way down. Javascript doesn't offer a mechanism for blocking on a promise. So if you've got async code your only options are .then and async/await, which is syntactic sugar for .then. This is different from how Promises (or the equivalent) work in many other languages, including C# and Java.
Finally a few stylistic things:
Javascript conventional style always puts a space between if and the parens around the conditional expression and a space between the conditional expression and block start. It's also convention to put a space between an object key and its value. And while both semi-colon and no-semicolon are common conventions, you should be consistent, using semicolons everywhere or nowhere

Acording to the axios docs, the axios method returns a promise, so all you have to do is add some async/await statements in the appropriate places: async on the getShipmentDetails function, and await for when its called, and an await on axios.
const axios = require('axios');
const SHIPMENT_AXIOS_CONFIG = { // Pulled this out for readability
method: 'post',
headers: { Authorization: '<Generated Bearer Token>'},
data: {
filter: {
type: 'postDispatch',
states: ['SHIPPED'],
orderDate: {
from: '2022-04-01',
to: '2022-04-04',
}
}
}
};
const getShipmentDetails = async (nextCall) => {
const res = await axios({ // Wait for the response
...SHIPMENT_AXIOS_CONFIG,
url: nextCall,
});
const { data } = res; // destructuring to get common properties makes the code more readable
if (data.hasMore == false) { // Should this be hasMore === true?
// Wait for the additional details
const shipments = await getShipmentDetails(data.nextPageUrl);
return data.shipments.concat(shipments);
}
return data.shipments;
};
const result = await getShipmentDetails('https://api.flipkart.net/sellers/v3/shipments/filter/');

Maybe a simpler approach would be by using do-while loop, something like this:
const axios = require('axios');
const callShipmentDetails = async (nextCall) => {
const res = await axios({...})
return res
}
const getShipmentDetails = (async (url = "https://api.flipkart.net/sellers/v3/shipments/filter/") => {
var hasMore = false
// var times = 0
do {
var response = await callShipmentDetails(url)
hasMore = response.data.hasMore
url = url.concat(response.data.nextPageUrl)
/*
this code is for breaking after 100 times if this becomes endless loop
*/
// ++times
// if (times > 100) break;
} while (hasMore)
});

Related

Cannot render and map POST request array promise

I have an API called getQuote and a component called QuoteCard. Inside QuoteCard I'm trying to render an array of users that liked a quote. The API works fine, I have tested it, and the code below for getting the users works fine too.
const Post = async (url, body) => {
let res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"accept": "*/*"
},
body: JSON.stringify(body)
}).then(r => r.json());
return res;
}
const getAllLikes = async () => {
let users = await Post('api/getQuote', {
id: "639e3aff914d4c4f65418a1b"
})
return users
}
console.log(getAllLikes())
The result is working as expected :
However, when trying to map this promise result array to render it onto the page is where I have problems. I try to render like this:
<div>
{getAllLikes().map((user) => (
<p>{user}</p>
))}
</div>
However, I get an error that states:
getAllLikes(...).map is not a function
I don't understand why this is happening. Why can't I map the array? Is it because it's a promise or something?
And if anyone needs to see the getQuote API, here it is:
//Look ma I wrote an API by myself! :D
import clientPromise from "../../lib/mongodb";
const ObjectId = require('mongodb').ObjectId;
import nc from "next-connect";
const app = nc()
app.post(async function getQuote(req, res) {
const client = await clientPromise;
const db = client.db("the-quotes-place");
try {
let quote = await db.collection('quotes').findOne({
_id: new ObjectId(req.body.id)
})
res.status(200).json(JSON.parse(JSON.stringify(quote.likes.by)));
} catch (e) {
res.status(500).json({
message: "Error getting quote",
success: false
})
console.error(e);
}
})
export default app
Thanks for any help!
It is due to the fact that getAllLikes is an async function and thus it returns promise which does not have a map function.
You can either save it in a state variable before using await Or chain it with .then.
Minimal reproducible example which works
const getAllLikes = async () => {
return ['a', 'b']
}
getAllLikes().then((r) => r.map((g) => { console.log(g) }))
Edit: The above code won't work if directly used with jsx since the return of getAllLikes will still be a promise. Solution would be to save it in a state variable and then using it.
I am from Angular and I believe we call pipe on Observables (or Promises). Map can then be called inside the pipe function
observable$ = getAllLikes().pipe(map( user => <p>{user}</p>))
If there is no pipe, I can only think of manually subscribing (which is not a good practice)
sub$ = getAllLikes().subscribe( user => <p>{user}</p>)
// unsub from sub$ appropriately
// We do this from ngOnDestroy in angular
ngOnDestroy() {
this.sub$?.unsubscribe()
}

chaining responses with axios and Vue js

I am trying to chain three requests in one with Axios. I am practicing vue.js so please correct me if my approach is not ideal.
The goal is to have three requests. One is a post followed by 2 Gets. What I would like to achieve is one chained request.
My questions are:
Is this a good way to handle this? Should they be chained together like this?
Is it possible to map the response to a model like I did in the first post request and pass it to the next?
const apiClient: AxiosInstance = axios.create({
headers: {
'content-type': 'application/json',
'X-RapidAPI-Key': '08f852e........22fb3e2dc0...',
'X-RapidAPI-Host': 'judge0-ce.p.rapidapi.com'
},
params: {base64_encoded: 'true', fields: '*'},
});
export const api = {
async submitCode(code: Code) {
code.language_id = 60
code.stdin = "Sn..2Uw"
code.source_code = btoa(code.source_code)
apiClient.post<Token>(`https://judge0-ce.p.rapidapi.com/submissions?language_id=${code.language_id}&source_code=${code.source_code}&stdin=SnVkZ2Uw`)
.then(function (response) {
console.log("res.data", response.data.token);
}).then(function (token) {
console.log("token", token); // <---- empty
`https://judge0-ce.p.rapidapi.com/submissions/${token}` // <---- get request
}).then(function (response) {
console.log("res.data", response);
}).then(function (response ) {
// here I will need a model
})
.catch((err) => {
const error = err.response ? err.response.data : err;
console.log("error" + error);
})
}
}
You have to await each function if the next one is dependent on the previous. Or you could use Promise chaining in the traditional sense using new Promise(resolve, reject). Async only applies to top level, so you will need to declare subsequent functions 'async' again as shown.
I would also suggest setting axios defaults with a base URL so you don't have to repeat the full URL each time. Note that console.log statements are not "Thenable" so your 1st statement has no effect, nor does your 3rd, other than to define your next variable.
const apiClient = axios.create({
baseURL: 'https://judge0-ce.p.rapidapi.com/submissons',
// ... headers ... //
});
export const api = {
async submitCode(code){
// ... code variable ...//
await apiClient.post(
`?language_id=${code.language_id}&source_code=${code.source_code}&stdin=SnVkZ2Uw`)
.then(async (response1) => {
const token = response1.data.token
await api.get(`/${token}`)
}).then(async (response2) => {
console.log(response2.data)
const model = response2.data.map((val1) => apiClient.get(`anotherGet/${val1.id}`))
const result = await Promise.all(model)
return result.map((val2) => val2.data)
})
// ... catch ...//
}}
You useasync, then don't chain promices
async function submitCode(code: Code) {
code.language_id = 60
code.stdin = "Sn..2Uw"
code.source_code = btoa(code.source_code)
try { // ~ global `.catch`
const token = await apiClient.post<Token>(`https://blah`)
const get1result = await apiClient.get<Blah>(`https://balah?${token}`)
const get2result = await apiClient.get<Blah>(`https://balah?${token}`)
doBlah({token, get1result, get2result})
} catch (err) { // maybe should check object type here
const error = err.response ? err.response.data : err;
console.log("error" + error);
}
}
As for Vue, I can only recomment to use asyncComputed which you can feet Promise into if you need that
Express also had express.api or something with which you can skip https://blah/com/api/ part of url, check it

I am getting a promise object in the output of a flask app I am working on. How can I get the value from the promise object?

So basically, I am using fetch to get the data from my database. I wrote a few JS functions that will take effect once the user clicks on the like button. One of those functions will post the new data to the server (that is what it is intended to do later on but for now, I am just printing the output). The output from that function is a promise object that is being returned to the server in the form of a json object. How can I get my desired output which is the VALUE of that promise?
The JavaScript code:
<script>
const likebtn = document.getElementById('likebtn');
const currentURL = window.location.href;
const likenum = document.getElementById('likenumber');
const postarray = currentURL.split("/");
const postName = postarray[4];
// console.log(postName)
function setLikeBtnColor() {
likebtn.style.color = JSON.parse(localStorage.getItem('likebtn')) ? 'cornflowerblue':'black';
}
setLikeBtnColor();
async function getlikenumber() {
const response1 = await fetch('/postlikes/'+postName)
fetchData = await response1.json()
likenum.textContent = fetchData.post_likes
return fetchData
}
async function getLikeNumber() {
const response = await fetch('/postlikes/'+postName)
fetchData = await response.json()
likenum.textContent = fetchData.post_likes
return fetchData
}
function myFunction() {
localStorage.setItem('likebtn', !JSON.parse(localStorage.getItem('likebtn')));
setLikeBtnColor();
if (likebtn.style.color === 'cornflowerblue') {
let currentLikeNum = getLikeNumber()
fetch('/postlikes/'+postName, {
method:"POST",
body: JSON.stringify({
post_likes:currentLikeNum+1
}),
headers:{
"Content-type":"application/json; charset=UTF-8"
}
})
getlikenumber()
} else {
let currentLikeNum = getLikeNumber();
fetch('/postlikes/'+postName, {
method:"POST",
body: JSON.stringify({
post_likes:currentLikeNum
}),
headers:{
"Content-type":"application/json; charset=UTF-8"
}
})
getlikenumber()
}};
likebtn.addEventListener('click', myFunction);
getlikenumber()
</script>
The python/flask code:
#app.route('/postlikes/<string:post_name>', methods=["GET","POST"])
def postlikes(post_name):
if request.method == "GET":
post = Image.query.filter_by(post_name=post_name).first()
# return "{'post likes':" + "'" + str(post.likes) + "'}"
return {"post_likes":post.likes}
else:
print(request.json)
My desired output:
{'post_likes':1}
or
{'post_likes':'1'}
the output I am getting:
{'post_likes': '[object Promise]1'}
P.S the initial value of the likes on the post_likes is 0.
getLikeNumber() returns a promise because an async function always returns a promise.
The keyword async just makes your whole function one big promise that await other promises inside its body, crudely abstracted.
So when you write :
let currentLikeNum = getLikeNumber() // currentLikeNum is a promise return
fetch('/postlikes/'+postName, {
method:"POST",
body: JSON.stringify({
post_likes:currentLikeNum+1 // currentLikeNum is a promise return converted to a string + "1"
So what you want is to either keep your function myFunction async and await your promise to keeps only the returns, like you already did with getLikeNumber()
Or with the old way :
getLikeNumber().then( res => {
//do your post here with the res variable
})
But that would not be consistent with your previous function.
In any case I would suggest to read one tutorial on async/await VS .then/.catch to continue your project comfortably.
Have a nice day !

Uncaught (in promise) TypeError: result.main is undefined

I am working on a weather based API project to sharpen my skills, but while fetching data i am getting a error:
Uncaught (in promise) TypeError: result.main is undefined
here is the function responsible for fetching data from API:
async function fetchData(cityName) {
const API_KEY = 'MY_API_TOKEN';
const fetchRes = {
method: 'GET',
redirect: 'manual',
mode: 'cors',
};
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${cityName}&appid=${API_KEY}`,
fetchRes
);
const result = await response.json();
const data = result.main.temp;
console.log(data);
}
fix using this call
fetchData("london");
here i am taking city name using another input and search button
note: i hide my API token key for Security Reasons so that's not the issue
separate effects
return the result instead of logging it -
async function fetchData(cityName) {
const API_KEY = 'MY_API_TOKEN';
const fetchRes = {
method: 'GET',
redirect: 'manual',
mode: 'cors',
};
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${cityName}&appid=${API_KEY}`,
fetchRes
);
const result = await response.json();
return result.main.temp // <-- return the result
}
Now attach effect and error handler -
fetchData(...)
.then(console.log) // <- receives data
.catch(console.error) // <- receives error
You can attach both the effect and error handler in a single .then -
fetchData(...)
.then(console.log, console.error) // <- same behaviour as above
separate concerns
And I would recommend separating concerns to make it easier to read/write your functions -
const getJSON = (url, opts = {}) =>
fetch(url, opts).then(r => r.json())
Now you don't have to repeat yourself every time you want to query some JSON -
async function fetchData(cityName) {
const API_KEY = 'MY_API_TOKEN';
const url = `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&appid=${API_KEY}`
const opts = {
method: 'GET',
redirect: 'manual',
mode: 'cors',
};
const result = await getJSON(url, opts) // <- reusable function
return result.main.temp
}
URL handling
I would advise against building URLs using strings. Instead use the URL searchParams API to construct things programmatically -
async function fetchData(cityName) {
const API_KEY = 'MY_API_TOKEN'
// use URL api
const u = new URL("https://api.openweathermap.org/data/2.5/weather")
u.searchParams.set("q", cityName)
u.searchParams.set("appid", API_KEY)
const opts = {...}
const result = await getJSON(u, opts) // <- pass URL object
return result.main.temp
}
As you can see it would be annoying to have to write all of that each time you need to set/modify some URL parameters. Applying the lesson from above, we write href -
function href(url, params = {}) {
const u = new URL(u)
for (const [key, value] of Object.entries(params))
u.searchParams.set(key, value)
return u.toString()
}
Now you can avoid repetitive and error-prone code in common functions -
async function fetchData(cityName) {
const API_KEY = 'MY_API_TOKEN'
// use our href helper
const u = href("https://api.openweathermap.org/data/2.5/weather", {
q: cityName,
appid: API_KEY
})
// use getJSON helper
const result = await getJSON(u, {...})
return result.main.temp
}
fetchData("someCity").then(console.log, console.error)

Why is my asynchronous input undefined in useEffect?

I'm writing a React application that fetches image data from a server for an array of URLs. I am storing the camera images as large strings that are placed into the image's src attribute. I am using useReducer to store my dictionary of camera objects.
I am having a couple of problems getting the reducer to work, and one of them has to do with some confusion I'm having with asynchronous values and why the async function returns correct output but the completion handler (.then()) receives undefined as a result.
Here is the code for useEffect() and the asynchronous fetching function.
useEffect()
//Why is cameras undefined?
useEffect(() => {
if (phase === 0) {
let cameras = {}
getCameraInformation().then((cameras) => {
debugger;
dispatch({
type: 'loadedCameraInformation',
payload: {cameras: cameras}
});
}).finally(() => setPhase(1))
}
});
My function signature and variables:
export default function Main() {
const [state, dispatch] = useReducer(cameraReducer, initialState);
let [phase, setPhase] = useState(0);
My function for getCameraInformation:
This returns a dictionary full of correct information!
async function getCameraInformation() {
//returns a json with the following: url, cam_name, cam_pass, cam_user, channel, chunk, group, path, port,
// uptime, username.
let cam_json = await axios
.get(getCamerasURL, { headers: { auth: get_cookie("token") } })
.then(response => {
let tempCameraArray = response.data.body;
let tempCameraDictionary = {};
for (var camera in tempCameraArray) {
tempCameraDictionary[tempCameraArray[camera].sid] = {
cameraInformation: tempCameraArray[camera],
cameraImage: null
};
}
return tempCameraDictionary;
})
.catch(error => console.log(error));
}
Your async function getCameraInformation doesn't have a return statement, so its promise will not resolve any value. There is a return in the then callback, but that's a different function entirely.
You are also using await and then() on the same promise, which isn't ideal. Use one or the other, because it's very easy to get confused when you mix and match here.
You already have an async, so don't use then at all in side that function.
async function getCameraInformation() {
//returns a json with the following: url, cam_name, cam_pass, cam_user, channel, chunk, group, path, port,
// uptime, username.
let response = await axios.get(getCamerasURL, { headers: { auth: get_cookie('token') } })
let tempCameraArray = response.data.body
let tempCameraDictionary = {}
for (var camera in tempCameraArray) {
tempCameraDictionary[tempCameraArray[camera].sid] = {
cameraInformation: tempCameraArray[camera],
cameraImage: null,
}
}
return tempCameraDictionary
}

Categories