Axios async problems [duplicate] - javascript

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
I have this axios call with a nested axios call that causes async problems. Here's a simplifyed snippet of my code:
axios.all([
axios.get('https://foo..', {
headers: { 'Content-Type': 'application/json' },
data: { 'endpoint': 'v2/bar..' },
}),
]).then(axios.spread((resp) => {
var resp = resp.data.content;
var items = resp.items.slice(0, 1);
items.forEach(item => {
var dataUrl = item.url.split('/v2/').slice(1).join('');
axios.get('https://foo..', {
headers: { 'Content-Type': 'application/json' },
data: { 'endpoint': 'v2/' + dataUrl + 'bar..' }
}).then(resp => {
const data = resp.data.content;
// alot of code goes here
}
)
}),
axios.get('https://foo..' + pageNum.toString(), {
headers: { 'Content-Type': 'application/json' },
data: {}
}
).then(resp => {
fillTemplate(resp.data.content[0].xml);
}
).catch(function (error) { console.log("DB-GET-Error:\n" + error) });
})
).catch(function (error) { console.log("error " + error) });
The problem is that 'fillTemplate()' executes before 'items.forEach(...)' is done and can populate 'fillTemplate()' with data, how can I solve this?

Problem
Both requests are send async and second won't wait for first one. Solution is to use async/await or Promise.all().
Escaping the callback hell
You are still having the problem of deeply nested code, which becomes unreadable. Below are two examples, first just fixing the issue and second with better readability.
Sample Code
axios.all([
axios.get('https://foo..', {
headers: { 'Content-Type': 'application/json' },
data: { 'endpoint': 'v2/bar..' },
}),
]).then(axios.spread((resp) => {
// cache axios promises here
var promises = []
var resp = resp.data.content;
var items = resp.items.slice(0, 1);
items.forEach(item => {
var dataUrl = item.url.split('/v2/').slice(1).join('');
var promise = axios.get('https://foo..', {
headers: { 'Content-Type': 'application/json' },
data: { 'endpoint': 'v2/' + dataUrl + 'bar..' }
}).then(resp => {
const data = resp.data.content;
// alot of code goes here
});
// add promises to array
promises.push(promise);
});
// wait for all promises to execute next request
Promise.all(promises).then((results) => {
axios.get('https://foo..' + pageNum.toString(), {
headers: { 'Content-Type': 'application/json' },
data: {}
}).then(resp => {
fillTemplate(resp.data.content[0].xml);
}).catch(function (error) { console.log("DB-GET-Error:\n" + error) });
});
})).catch(function (error) { console.log("error " + error) });
Refactored version
axios.all([doStuff()]).then(axios.spread((resp) => {
var promises = []
var resp = resp.data.content;
var items = resp.items.slice(0, 1);
items.forEach(item => {
var dataUrl = item.url.split('/v2/').slice(1).join('');
var promise = doOtherStuff();
promises.push(promise);
});
Promise.all(promises).then(finalCallback).then(resp => {
fillTemplate(resp.data.content[0].xml);
});
}));
function doStuff() {
return axios.get('https://foo..', {
headers: { 'Content-Type': 'application/json' },
data: { 'endpoint': 'v2/bar..' },
});
}
function doOtherStuff() {
return axios.get('https://foo..', {
headers: { 'Content-Type': 'application/json' },
data: { 'endpoint': 'v2/' + dataUrl + 'bar..' }
}
}
function finalCallback(results) {
return axios.get('https://foo..' + pageNum.toString(), {
headers: { 'Content-Type': 'application/json' },
data: {}
});
}

Related

Returning results of a recursive fetch for SharePoint List REST

I have this function, but the results are returning undefined. Seems like I'm missing something that isn't fulling the return.
I've tried a few things, but still have the same results. What am I missing?
function spGetListColumns(listTitle, siteURL = _spPageContextInfo.webAbsoluteUrl) {
var listGetURL = siteURL + "/_api/web/lists/getbytitle('" + listTitle + "')/fields?$select=Title,TypeAsString,TypeDisplayName,Required&$filter=Hidden eq false and ReadOnlyField eq false";
var getOptions = {
method: 'GET',
headers: new Headers({
'Accept': 'application/json; odata=verbose',
'content-type': 'application/json; odata=verbose'
})
};
var spResults = [];
spGetItems(listGetURL);
function spGetItems(newListURL) {
return fetch(newListURL, getOptions).then(response => response.json()).then(data => {
spResults = spResults.concat(data.d.results);
if (data.d.__next) {
newListURL = data.d.__next;
return spGetItems(newListURL);
} else {
return spResults;
}
}).catch(error => {
console.error(error);
});
}
}
The result returned in fetch is a promise. I made a bit change to your code:
function spGetListColumns(listTitle, siteURL = _spPageContextInfo.webAbsoluteUrl) {
var listGetURL = siteURL + "/_api/web/lists/getbytitle('" + listTitle + "')/fields?$select=Title,TypeAsString,TypeDisplayName,Required&$filter=Hidden eq false and ReadOnlyField eq false";
var getOptions = {
method: 'GET',
headers: new Headers({
'Accept': 'application/json; odata=verbose',
'content-type': 'application/json; odata=verbose'
})
};
var spResults = [];
return spGetItems(listGetURL)
function spGetItems(newListURL) {
return fetch(newListURL, getOptions).then(response => response.json()).then(data => {
spResults = spResults.concat(data.d.results);
if (data.d.__next) {
newListURL = data.d.__next;
spGetItems(newListURL);
} else {
console.log(spResults);
return spResults
}
}).catch(error => {
console.error(error);
});
}
}
Call the spGetListColumns function:
spGetListColumns('List1').then(res=>{console.log(res)})

Issues with returning values from nested axios call in ReactJs

The below function does not return any value when the lenght of the output from the first call. I have given comment in the line of code that is creating issues in the below snippet. Can you please advise what is the issue with my code?
async createProfileByUserIDNew(data,email) {
const AuthStr = 'Bearer ' + getToken();
const response = await axios
.get(`${baseUrl}/profiles?email=${email}`, {
headers: { Authorization: AuthStr },
})
.then((response) => {
if (response.data.length===0){
return axios
.post(`${baseUrl}/buyer-profiles`, data, {
headers: { Authorization: AuthStr },
})
}else{
console.log(response.data); // Printing the proper results
return response.data, // not returning any results to next then.Return statement not working
}
}
}).then((response) => {
return{
items: response.data, // returning proper results for if statement but failing for else condition
}
})
.catch((error) => (console.log( JSON.stringify(error)) ));
}
///calling the nested axios call
const ret = MyProfileRepository.createProfileByUserIDNew(
data,
user.email
);
ret.then(function (response) {
console.log(response); //Printing 'undefined'
This.setState({ buyerProfileId: response.items.id });
});
Your createProfileByUserIDNew is an async function, meaning you need a return statement:
async function createProfileByUserIDNew() {
const AuthStr = 'Bearer ' + getToken();
const response = await axios.get(...).etc;
return response;
}
Alternatively, you can also directly return a promise:
async function createProfileByUserIDNew() {
const AuthStr = 'Bearer ' + getToken();
return axios.get(...).etc;
}
You should have only one .then callback function. Here is the correct implementation
async createProfileByUserIDNew(data,email) {
const AuthStr = 'Bearer ' + getToken();
const response = await axios
.get(`${baseUrl}/profiles?email=${email}`, {
headers: { Authorization: AuthStr },
})
.then((response) => {
if (response.data.length===0){
return axios
.post(`${baseUrl}/buyer-profiles`, data, {
headers: { Authorization: AuthStr },
}).then((response) => {
return{
items: response.data, // returning proper results.working as expected
}
})
.catch((error) => (console.log( JSON.stringify(error)) ));
}
}else{
console.log(response.data[0]); // Printing the proper results
return{
items: response.data[0], // not returning any results.Return statement not working
}
}
})
But you can improve your conditions but for the sake of argument, this code will work.
The problem is that when you return from your first then function, the promise falls through to the second then function. The else condition of your first function returns an object shaped as { items: data } while the second then function is expecting a response object, not an object w/ an items key.
You could handle this in a few ways:
You're defining an async function, but you're not doing anything asynchronously (in terms of ES6 syntax). You could convert your whole function to this syntax to async/await properly within your conditional statements.
You could convert your first then to an async function, then asynchronously fetch the second request from there.
You could change the structure of what you return in the else condition so that it looks like a response object, and can be parsed properly in the secondary then function.
In addition, you are not returning the response promise.
Solution 1
async createProfileByUserIDNew(data, email) {
const AuthStr = 'Bearer ' + getToken();
try {
let response = axios.get(`${baseUrl}/profiles?email=${email}`, {
headers: { Authorization: AuthStr },
});
if (response.data.length === 0) {
response = await axios.post(`${baseUrl}/buyer-profiles`, data, {
headers: { Authorization: AuthStr },
});
}
return {
items: response.data[0],
};
} catch (error) {
console.log(JSON.stringify(error));
}
}
Solution 2
createProfileByUserIDNew(data,email) {
const AuthStr = 'Bearer ' + getToken();
return axios
.get(`${baseUrl}/profiles?email=${email}`, {
headers: { Authorization: AuthStr },
})
.then(async (response) => {
if (response.data.length === 0) {
response = await axios
.post(`${baseUrl}/buyer-profiles`, data, {
headers: { Authorization: AuthStr },
});
}
return {
items: response.data[0],
};
})
.catch((error) => {
console.log(JSON.stringify(error));
});
}
Solution 3
createProfileByUserIDNew(data,email) {
const AuthStr = 'Bearer ' + getToken();
return axios
.get(`${baseUrl}/profiles?email=${email}`, {
headers: { Authorization: AuthStr },
})
.then((response) => {
if (response.data.length === 0) {
return axios
.post(`${baseUrl}/buyer-profiles`, data, {
headers: { Authorization: AuthStr },
});
} else {
return {
response: {
data: {
items: response.data[0],
},
},
};
}
}).then((response) => {
return {
items: response.data,
};
})
.catch((error) => {
console.log(JSON.stringify(error));
});
}

Variable is not updating after ajax request

I have the code below which is in a Vue script.
user_id = 100; //sample data
$.ajax({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
type: "GET",
url: '/user',
success: function (user) {
user_id = user.user_id;
console.log(user_id); //returns 1
},
error: function (result) {
}
});
console.log(user_id); //returns 100 not 1
I want to be able to store the value that is resulted from the ajax request which is 1. However, when I console.log at the end of the script it returns 100 not 1. I think that I need to use a promise/callback to solve this but I am not sure how/what I need to do. Can someone help me?
Define your method and return as a promise.
function getUsers() {
return new Promise((resolve, reject) => {
$.ajax({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
type: "GET",
url: '/user',
success: function (data) {
resolve(data);
},
error: function (error) {
reject(error);
}
});
});
}
You would call the method as below.
getUsers().then((data) => {
console.log(data); /* you will get the new data returned from ajax.*/
}).catch((error) => {
console.log(error);
});
This is how you can promisify callbacks in general:
let doXWithCallback = callback => {
// do x...
callback();
};
let doXPromisified = () => new Promise(doXWithCallback);
doXWithCallback(() => console.log('do x with callback'));
doXPromisified().then(() => console.log('do x promisified'));
For your example specifically:
let doRequest = () =>
new Promise((resolve, reject) =>
$.ajax({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
type: "GET",
url: '/user',
success: user => resolve(user.user_id),
error: reject(),
}));
doRequest.then(userId => console.log('userId is', userId));

How to get response from https.request in node outside of function [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I am trying to do something that I just can't wrap my head around, I have tried doing something with promises but still get stuck. I tried reading through this and still am stuck How do I return the response from an asynchronous call?
What I need to do is run the following code and get the body outside of the req, so I can check if it is succesful and send a response in a lambda with a 200 and a message from the body. I don't want to return the 200 status inside the function because I need to also check if a fetch request is succesful before sending the 200 status with a body back.
Hopefully someone can help with this
let statusTrue
const req = https.request(options, function(res) {
res.setEncoding("utf8")
res.on("data", function(body) {
console.log(`Body: ${body}`)
statusTrue = body.status
})
})
if (statusTrue) {
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
},
body: JSON.stringify({ email: true }),
}
} else {
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
},
body: JSON.stringify({ email: false }),
}
}
Update Heres my code with oswins answer, the full code for context. Right now the function fires after the handler finishes so I never am able to send a proper response back.
const sgMail = require("#sendgrid/mail")
require("dotenv").config()
const { SENDGRID_API_KEY, SENDGRID_TO_EMAIL } = process.env
const URL = require("url")
const https = require("https")
const fetch = require("node-fetch")
exports.handler = async (event, context) => {
try {
//console.log(JSON.parse(event.body))
//*** send off to google to verify captcha */
const body = JSON.parse(event.body)
let secret = "fdsf"
const fetchUrl = `https://www.google.com/recaptcha/api/siteverify?secret=${secret}&response=${body.captcha}&remoteip=${event.headers["client-ip"]}`
let isItTrue
await fetch(fetchUrl, {
method: "POST",
body: JSON.stringify({ message: "hello world" }),
headers: { "Content-Type": "application/json" },
})
.then(response => response.json())
.then(data => {
isItTrue = data.success
})
.catch(error => {
console.error("Error:", error)
})
//console.log(isItTrue)
//*** end */
//*** Running Form Sends Now if Captcha Valid */
if (isItTrue) {
//*** Zapier Send */
const webhook_url = URL.parse(
"https://hooks.zapier.com/hooks/catch/fds"
)
const options = {
hostname: webhook_url.hostname,
path: webhook_url.pathname,
method: "POST",
headers: { "Content-Type": "application/json" },
}
// Set up webhook request
const req = https.request(options, function(res) {
res.setEncoding("utf8")
res.on("data", function(body) {
console.log(`Body: ${body}`)
sendResponse(body.status)
})
})
// Handle webhook request error
req.on("error", function(e) {
const errorMessage = `[ERROR] Problem with request: ${e.message}`
console.log(errorMessage)
callback(e.message, {
statusCode: 400,
body: errorMessage,
})
})
// Send form data to webhook request and end request
req.end(JSON.stringify(body.data))
//*** End */
//console.log(zapierStatus)
const sendResponse = statusTrue => {
if (statusTrue === "success") {
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
},
body: JSON.stringify({ email: true }),
}
} else {
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
},
body: JSON.stringify({ email: false }),
}
}
}
} else {
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
},
body: JSON.stringify({ captcha: false }),
}
}
//*** end */
} catch (err) {
return { statusCode: 500, body: err.toString() }
}
}
Maybe wrapping statements outside the function into another function will help ;-)
const req = https.request(options, function (res) {
res.setEncoding("utf8")
res.on("data", function (body) {
console.log(`Body: ${body}`)
statusTrue = body.status
sendResponse(statusTrue)
})
})
function sendResponse(statusTrue) {
if (statusTrue) {
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
},
body: JSON.stringify({ email: true }),
}
} else {
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
},
body: JSON.stringify({ email: false }),
}
}
}
You can wrap your https.request call in a promise like this:
const makeRequest = function(options) {
return new Promise(function(resolve, reject) {
const req = https.request(options, function(res) {
res.setEncoding("utf8")
res.on("error", reject)
res.on("data", function(body) {
console.log(`Body: ${body}`)
resolve(body.status)
})
})
}
Then later you can do:
makeRequest({/*options*/})
.then(function(statusTrue) {
if (statusTrue) {
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
},
body: JSON.stringify({ email: true }),
}
} else {
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
},
body: JSON.stringify({ email: false }),
}
}
})
.then(function(ret) {
console.log(ret)
})
.catch(function(err) {
/* handle error */
})

Node + ES6: How to use Promise.all with async requests?

I have a method called fetchMerchantData which calls 3 other async methods. I'm trying to use Promise so that it doesn't call resp.direct(301, ...) until all the requests are finished but it's not working.
function fetchOauth2Token(authorizationCode, resp) {
...
request({
url: `https://connect.squareup.com/oauth2/token`,
method: "POST",
json: true,
headers: oauthRequestHeaders,
form: oauthRequestBody,
}, (error, oauthResp, body) => {
if (body.access_token) {
Promise.resolve(fetchMerchantData(body.access_token, body.merchant_id)).then(() => {
console.log("last!"); //<--------------------- this is printing first
resp.redirect(
301,
`myurl.com/blah`
);
});
;
} else {
// TODO find out what to do on failure
resp.redirect(301, `myurl.com/?error=true`);
}
})
}
function fetchMerchantData(access_token, merchant_id){
const updates = {};
request({
url: `https://connect.squareup.com/v1/me/locations`,
method: "GET",
json: true,
headers: {
Authorization: `Bearer ${access_token}`,
Accept: 'application/json',
"Content-Type": "application/json",
},
}, (error, response) => {
if (!error) {
const locations = response.body;
Promise.all([
saveMerchant(merchant_id, access_token, locations),
saveLocations(merchant_id, locations),
installWebhookForLocations(access_token, locations),
]).then(values => {
console.log("first!"); //<--------------------- this is printing last
return Promise.resolve("Success");
})
}
});
}
And here's an example of the saveMerchant method which calls firebase:
function saveMerchant(merchant_id, access_token, locations) {
const merchantRef = database.ref('merchants').child(merchant_id);
const location_ids = locations.map(location => location.id);
merchantRef.update({
access_token,
location_ids,
});
}
How would I synchronize this?
== UPDATE ==
This is how my installWebhookForLocations method looks:
function installWebhookForLocations(access_token, locations){
const locationIds = locations.map(location => location.id);
locationIds.forEach((locationId) => {
request({
url: `https://connect.squareup.com/v1/${locationId}/webhooks`,
method: "PUT",
json: true,
headers: {
Authorization: `Bearer ${access_token}`,
Accept: 'application/json',
"Content-Type": "application/json",
},
body: ["PAYMENT_UPDATED"],
}, (error) => {
if (!error){
console.log(`Webhook installed for ${locationId}`);
}
});
});
}
Here is an example of saveMerchant that would use a promise.
function saveMerchant(merchant_id, access_token, locations) {
return new Promise(function (resolve, reject) {
const merchantRef = database.ref('merchants').child(merchant_id);
const location_ids = locations.map(location => location.id);
merchantRef.update({
access_token,
location_ids,
}, function (error) {
if (error) return reject(error);
resolve();
});
});
}
To make the above easier, there is a nice Promise library called Bluebird, it has a promisify utility, that you could apply to firebird update method.
Also for your second question were your using forEach, bluebird has a nice utility function called map that you could use instead.

Categories

Resources