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
Related
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()
}
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)
});
I have a utils layer with some functions inside like getUserInfo(userToken). I call this getUserInfo function to get userId. I get Node js works asynchronously, But is there any way I can achieve the below functionality. I am using npm request library to make rest calls.
routes/controller.js receives call with userToken. Make a call to getUserInfo() in utils/users.js to get UserId. getUserInfo function makes a request.get('url', function (e, r, b)) call to external service.
I am trying to implement the following, But Not interested in using callbacks and having nested stuff.
//Controller Layer Part
router.get('/api', check, async function (req, res) {
const userId = await users.getUserInfo(req.headers.authorization);
const result = await assignmentsService.getAssignments(userId);
res.send(result);
});
//Utils Layer Part
async function getUserInfo(accessToken, callback){
const userId = await request.get({
url: 'someUrl',
headers: {
'Authorization': accessToken
}
},
await function (error, response, body){
const userId = JSON.parse(body).nickname;
});
return userId;
}
Per your request. I would recommend axios for you.
Example below:
const axios = require("axios");
//Controller Layer Part
router.get("/api", check, async function (req, res) {
const userId = await users.getUserInfo(req.headers.authorization);
const result = await assignmentsService.getAssignments(userId);
res.send(result);
});
//Utils Layer Part
async function getUserInfo(accessToken) {
const response = await axios.get("someUrl", {
headers: {
Authorization: accessToken,
},
});
console.log(response);
const userId = JSON.parse(response.data).nickname; //it may not be response.data but you can change.
return userId;
}
Consider the following Javascript/React code:
// Javascript function that has a fetch call in it.
export const signIn = (email:string, password:string) => {
console.log("FETCHING...");
fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
.then((response) => {
return response.json()
})
.then(({ data }) => {
console.log("FETCHED DATA...")
})
.catch((error) => {
console.error('ERROR: ', error)
})
console.log("DONE FETCHING...");
}
// A functional component that references signIn.
export const SignIn: React.FC<Props> = () => {
// irrelevant code ...
const onSubmit = (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
signIn(email, password, setAuthentication, setCurrentUser)
console.log("SIGNED IN...");
}
return <>A form here submits and calls onSubmit</>
}
This produces the following console log output:
SIGNING IN...
FETCHING...
DONE FETCHING...
SIGNED IN...
FETCHED DATA...
I want FETCHED DATA... to show up before DONE FETCHING.... I've tried playing around with aysnc/await but that's not working so I don't know where to go from here.
Just add another .then
.then((response) => {
return response.json()
})
.then(({ data }) => {
console.log("FETCHED DATA...")
return
}).then(()=> {
console.log("DONE FETCHING...");
})
.catch((error) => {
console.error('ERROR: ', error)
})
It would have to be in the then statements if you want the console.log to wait until the promise is resolved. Here's an example that uses async/await:
export const signIn = async (email:string, password:string) => {
console.log("FETCHING...");
const response = await fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
const data = await response.json();
console.log("FETCHED DATA...")
console.log("DONE FETCHING...");
}
You would also need to turn this into an async function if you want the console.log to happen after the data is done fetching:
const onSubmit = async (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
await signIn(email, password, setAuthentication, setCurrentUser)
console.log("SIGNED IN...");
}
In order to use async await, you need to return a promise from the call. So basically you don't execute the .then and wrap the call in a try catch block.
export const signIn = async (email:string, password:string) => {
console.log("FETCHING...");
return fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
}
and
const onSubmit = async (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
try {
const data = await signIn(email, password, setAuthentication, setCurrentUser)
// Parse data, do something with it.
console.log("SIGNED IN...");
} catch (e) {
// handle exception
}
}
You may want to look more into how promises in JavaScript works.
One problem here is in signIn. What you're doing right now is:
function signIn() {
// 1. log FETCHING
// 2. call asynchronous fetch function
// 3. log DONE FETCHING
}
The key here is that fetch is asynchronous. The program doesn't wait for it to finish before moving on. See the problem? The JavaScript interpreter is going to run step 3 without waiting for step 2 to finish.
There are multiple ways to fix this. First, you can use then. Here's an example:
promise
.then(res => func1(res))
.then(res => func2(res))
.then(res => func3(res))
Here, you're telling JavaScript to:
Run promise, and wait for it to resolve.
Take the result from promise and pass it into func1. Wait for func1 to resolve.
Take the result from func1 and pass it into func2. Wait for func2 to resolve.
etc.
The key difference here is that you are running each then block in order, waiting for each previous promise to be resolved before going to the next one. (Whereas before you didn't wait for the promise to resolve).
Your code with promises would look like:
export const signIn = (email: string, password: string) => {
console.log("FETCHING...")
// Note that we return the promise here. You will need this to get onSubmit working.
return fetch(/* args */)
.then(res => res.json())
.then(({ data }) => console.log("DONE FETCHING"))
.catch(err => /* HANDLE ERROR */)
}
The second way to fix this is to use async and await. async and await is simply syntax sugar over promises. What it does underneath is the exact same, so make sure you understand how promises work first. Here's your code with async and await:
// The async keyword here is important (you need it for await)
export const signIn = async (email: string, password: string) => {
console.log("FETCHING...");
try {
const res = await fetch(/* args */) // WAIT for fetch to finish
const { data } = res.json()
console.log("FETCHED DATA...")
} catch (err) {
/* HANDLE ERROR */
}
console.log("DONE FETCHING...")
}
There's also a second similar problem in onSubmit. The idea is the same; I'll let you figure it out yourself (the important part is that you must return a Promise from signIn).
i'm working with an API that allows me to sync data to a local DB. There is a syncReady API that I'm calling recursively until the sync batch is ready to start sending data. The recursion is working correctly and the .then callback is called, but the resolve function never resolves the response.
const request = require('request-promise');
const config = require('../Configs/config.json');
function Sync(){}
Sync.prototype.syncReady = function (token, batchID) {
return new Promise((res, rej) => {
config.headers.Get.authorization = `bearer ${token}`;
config.properties.SyncPrep.id = batchID;
request({url: config.url.SyncReady, method: config.Method.Get, headers: config.headers.Get, qs: config.properties.SyncPrep})
.then((response) => {
console.log(`The Response: ${response}`);
res(response);
}, (error) => {
console.log(error.statusCode);
if(error.statusCode === 497){
this.syncReady(token, batchID);
} else rej(error);
}
);
});
};
I get the 497 logged and the "The Response: {"pagesTotal";0}" response but the res(response) never sends the response down the chain. I've added a console.log message along the entire chain and none of the .then functions back down the chain are firing.
I hope I've explained this well enough :-). Any ideas why the promise isn't resolving?
Thanks!
First, you don't need to wrap something that returns a promise with a new Promise. Second, for your error case you don't resolve the promise if it is 497.
const request = require('request-promise');
const config = require('../Configs/config.json');
function Sync(){}
Sync.prototype.syncReady = function (token, batchID) {
config.headers.Get.authorization = `bearer ${token}`;
config.properties.SyncPrep.id = batchID;
return request({url: config.url.SyncReady, method: config.Method.Get, headers: config.headers.Get, qs: config.properties.SyncPrep})
.then((response) => {
console.log(`The Response: ${response}`);
return response;
})
.catch((error) => {
console.log(error.statusCode);
if(error.statusCode === 497){
return this.syncReady(token, batchID);
} else {
throw error;
}
})
);
};
Maybe something like the above will work for you instead. Maybe try the above instead. As a general rule of thumb, it's you almost always want to return a Promise.