Interference in data retrieval - javascript

I have three functions as shown:
async doAction (link, state)
{
try {
let response = await fetch(
'http://xxx.tld/'+link,
{
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type:1
})
}, {timeout: 500});
let responseJson = await response.json();
alert(state)
switch (state) {
//somethings
.
.
.
default:
}
} catch (error) {
//alert(error);
}
}
async componentDidMount(){
this.doAction('loadModAdv.php', 1)
}
async loadModAdv(object){
object.map(async (data)=>{
if(data.status == "1")
{
await this.doAction(data.fileName, parseInt(data.moduleAdvertise, 10) + 1)
}
})
}
When "await this.doAction" is executed in the map, the "alert(state)" does not return the data in sequence.
Sometimes 1 2 4 and sometimes 1 4 2
What is the solution?

To achieve consistent/deterministic asynchronous iteration over an array, consider updating your code to use the following pattern:
async loadModAdv(object){
for(const data of object) {
if(data.status == "1") {
await this.doAction(data.fileName, parseInt(data.moduleAdvertise, 10) + 1)
}
}
}
As a note, I noticed you used the map function to iterate however it didn't seem that you're using the result of the map, so in this answer I've opted for a simpler solution based on a for loop.
If you wanted to achieve "map-like" behaviour via this pattern, you could do something like this:
async loadModAdv(object){
var results = []
for(const data of object) {
if(data.status == "1") {
results.push(await this.doAction(data.fileName, parseInt(data.moduleAdvertise, 10) + 1))
}
}
console.log('data in order ', JSON.stringify(results))
}
Hope that helps!

Related

How can I stop a loop from completing once it gets the first returned object from an API?

The code below receives all of the user's redeemed rewards from an API, I need to stop the loop from completing them all at once.
The for loop runs through all of the rewards the current user has redeemed through Twitch's API, then fulfills them if specific conditions are met. I want it to only fulfill one redemption, not all (x) amount of them.
The fulfill reward part happens at: fulfillReward()
For a full code snippet, click here: https://pastebin.com/7k5WNhmD
// looping over reward returned data
for (let i = 0; i < rewards.length; i++) {
async function fulfillReward() {
await fetch(
`https://api.twitch.tv/helix/channel_points/custom_rewards/redemptions?broadcaster_id=58606718&reward_id=08d5e2d9-ddd7-4082-bc78-39b06b35cd68&id=${rewards[i].id}`,
{
method: 'PATCH',
headers: {
'client-Id': process.env.TWITCHBOT_CLIENT_ID,
Authorization: `Bearer ${process.env.TWITCHBOT_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
status: 'FULFILLED',
}),
}
)
}
const currentReward = rewards[i]
const currentRewardUsername = currentReward.user_name.toLowerCase()
if (currentRewardUsername === input.toLowerCase()) {
countDocuments(discordID)
.then(() => {
return findOneUser(discordID)
})
.then(() => {
// All (x) amount of rewards get fulfilled instead of the first matching result
fulfillReward()
interaction.reply('success')
})
.catch((err) => console.log(err))
} else if (currentRewardUsername != input.toLowerCase()) {
return interaction.reply(`The Twitch user **${input}** has not redeemed the channel reward!`)
}
}
The general solution to exit a loop when a condition is met, is to incorporate a break statement in whatever conditional decides the loop has achieved its purpose early.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break
I've made a simple snippet that runs a million cycle for-next loop but terminates after 10 loops when a condition is met, using break.
let loopNumber = 0;
for (let i=0; i<1000000; i++)
{
loopNumber++;
if (loopNumber==10) {
break;
}
} // next i;
console.log(loopNumber);
Not sure I understood completely what you actually want but lets try:
async function fulfillReward(reward) {
await fetch(
`https://api.twitch.tv/helix/channel_points/custom_rewards/redemptions?broadcaster_id=58606718&reward_id=08d5e2d9-ddd7-4082-bc78-39b06b35cd68&id=${reward.id}`,
{
method: 'PATCH',
headers: {
'client-Id': process.env.TWITCHBOT_CLIENT_ID,
Authorization: `Bearer ${process.env.TWITCHBOT_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
status: 'FULFILLED',
}),
}
)
}
If you just receive the reward as a parameter you don't need to declare functions inside the for (please don't ever do that)
Then you call it sending the reward parameter
if (currentRewardUsername === input.toLowerCase()) {
countDocuments(discordID)
.then(() => {
return findOneUser(discordID)
})
.then(() => {
fulfillReward(currentReward)
interaction.reply('success')
})
.catch((err) => console.log(err))
} else if (currentRewardUsername != input.toLowerCase()) {
return interaction.reply(`The Twitch user **${input}** has not redeemed the channel reward!`)
}
I updated your code here https://pastebin.com/clone/7k5WNhmD, but cant really test it, so please let me know if you need something else
try this.
for (let i = 0; i < rewards.length; i++) {
async function fulfillReward() {
await fetch(
`https://api.twitch.tv/helix/channel_points/custom_rewards/redemptions?broadcaster_id=58606718&reward_id=08d5e2d9-ddd7-4082-bc78-39b06b35cd68&id=${rewards[i].id}`,
{
method: 'PATCH',
headers: {
'client-Id': process.env.TWITCHBOT_CLIENT_ID,
Authorization: `Bearer ${process.env.TWITCHBOT_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
status: 'FULFILLED',
}),
}
)
}
const currentReward = rewards[i]
const currentRewardUsername = currentReward.user_name.toLowerCase()
if (currentRewardUsername === input.toLowerCase()) {
function doSomething() {
return new Promise((resolve, reject) => {
countDocuments(discordID)
.then(() => {
return findOneUser(discordID)
})
.then(() => {
// All (x) amount of rewards get fulfilled instead of the first matching result
fulfillReward()
interaction.reply('success')
resolve(true);
})
.catch((err) => console.log(err))
})
}
const ret = await doSomething();
if (ret)
return ;
} else if (currentRewardUsername != input.toLowerCase()) {
return interaction.reply(`The Twitch user **${input}** has not redeemed the channel reward!`)
}
}

How to return values from a nested Axios call?

I am trying to perform the below steps:
Step 1: Make Axios call to check if record exists in database.
Step 2: If the record does not exit then make a POST API call to create data and return POST response.
Step 3: If the record already exists then return the response from Step 1
Step 1 and Step 2 works fine and I am able to return the value from createProfileByUserIDNew. When code block for Step 3 gets executed then createProfileByUserIDNew is not returning any value.
Can someone tell me what am I doing wrong?
async createProfileByUserIDNew(data, email) {
const AuthStr = 'Bearer ' + getToken();
const response = await axios
.get(`${baseUrl}/buyer-profiles?user_email=${email}`, {
headers: { Authorization: AuthStr },
})
.then((response) => {
//*****This block return proper value in the next then
if (response.data.length === 0) {
return axios.post(`${baseUrl}/buyer-profiles`, data, {
headers: { Authorization: AuthStr },
});
//~~~~~~~This block return proper value in the next then
//*****This block does not return any value in the next then
} else {
return response //Return response from first axios call
}
//*****This block does not return any value in the next then
})
.then((response) => {
return (response); //Step 2 return the value but step 3 return undefined
})
.catch((error) => console.log(JSON.stringify(error)));
}
Calling the above method:
const ret = createProfileByUserIDNew(
data,
user.email
);
ret.then(function (response) {
console.log(response); //Setp 2 returns proper value but step 3 return undefined
this.setState({ buyerProfileId: response.items.id });
});
Remember that async/await is "syntactic sugar" for the other promise syntax of chaining .then(), .catch(), and .finally(); in other words, it allows you to handle these types of asynchronous operations in code that appears more synchronous.
const createProfileByUserIDNew = async (data, email) => {
const AuthStr = "Bearer " + getToken();
try {
// we're awaiting this response, so we don't need to chain a .then()
// we could even destructure response into the objects we'll need later,
// i.e. const { data } = await axios.get(...)
const response = await axios.get(
`${baseUrl}/buyer-profiles?user_email=${email}`,
{
headers: { Authorization: AuthStr },
}
);
if (response.data.length === 0) {
// do the things we need to do when we don't get the data we want
// once again, we don't have to chain a then() to this; you may
// have heard that 'return await' is redundant and causes some
// problems, but since we're in a try/catch it's ok
// see https://jakearchibald.com/2017/await-vs-return-vs-return-await/
return await axios.post(`${baseUrl}/buyer-profiles`, data, {
headers: { Authorization: AuthStr },
});
} else {
// the user exists, so we'll do other things, like maybe return the
// original response or something
return response;
}
} catch (error) {
console.error("We hit a snag:", error);
}
};
// now when we call this method (must be called from another async function), the same principles apply
const ret = await createProfileByUserIDNew(data, user.email);
console.log(ret);
This.setState({ buyerProfileId: ret.data.items.id });
That happens because when you simply do return response you're returning the response of the request, not a Promise. You should only chain the .then() when making the axios.post() call since that actually returns a Promise.
Also, if you want to use the createProfileByUserIDNew function the way you are currently you need to return the Promise from axios.get directly.
async createProfileByUserIDNew(data, email) {
const AuthStr = 'Bearer ' + getToken();
return axios.get(`${baseUrl}/buyer-profiles?user_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 response;
});
} else {
return response;
}
})
.catch((error) => console.log(JSON.stringify(error)));
}

Async function immediately call then() in recursive function

I have a problem, I tried to use async function to make API call but then() doesn't wait until the async function return the promise.
async function :
async function FgetFloorplansByFolder (idProject,idFolder, data = [], hasMore = false, lastSyncedAt = null) {
axios.get(API_URL, {
params:{
'last_synced_at':lastSyncedAt
},
headers: {
'Authorization': API_TOKEN,
'Accept': 'application/json'
}
})
.then((response) => {
let XHasMore = response.headers['x-has-more'];
let lastSyncedAt = response.headers['x-last-synced-at'];
for(var i in response.data) {
if(response.data[i].folder_id != null || response.data[i].folder_id == idFolder){
data.push(response.data[i])
}
}
if(XHasMore == 'true'){
FgetFloorplansByFolder(idProject,idFolder, data, XHasMore, lastSyncedAt)
}
else {
console.log(data);
return data
}
})
.catch((err) => {
return Promise.reject(err)
})
}
call of async function :
await FgetFloorplansByFolder(req.params.idProject, req.params.idFolder)
.then((result) => {
console.log(result);
})
.catch((error)=>{
console.log(error);
})
The expected result is : then function in the call wait until getFloorplansByFolders finish his recursive call and return data before print result in then. But then is printing undefined and doesn't wait until async function finish his call.
How can I do ?
Nothing in the code tells the function that it should wait for that promise to settle, so it doesn't.
In general, don't mix async/await with .then/.catch/.finally (though there are exceptions), use one or the other.
In this case, you can either
Remove the async and just put return in front of the call to axios to return the promise chain; or
Switch to using await within the function
(In both cases, I strongly urge you to remove the .catch handler that converts rejection into fulfillment with undefined; instead, let the caller see the rejection so they know the operation failed.)
#1 looks something like this (note comments):
// 1. No `async`
function FgetFloorplansByFolder (idProject,idFolder, data = [], hasMore = false, lastSyncedAt = null) {
// 2. Return the promise chain
return axios.get(API_URL, {
params:{
'last_synced_at':lastSyncedAt
},
headers: {
'Authorization': API_TOKEN,
'Accept': 'application/json'
}
})
.then((response) => {
let XHasMore = response.headers['x-has-more'];
let lastSyncedAt = response.headers['x-last-synced-at'];
for(var i in response.data) {
if(response.data[i].folder_id != null || response.data[i].folder_id == idFolder){
data.push(response.data[i])
}
}
if(XHasMore == 'true'){
// 3. Return the promise from the recursive call
return FgetFloorplansByFolder(idProject,idFolder, data, XHasMore, lastSyncedAt)
}
else {
console.log(data);
return data
}
});
// 4. Don't put a `.catch` here -- let the caller know the operation failed
}
#2 looks something like this:
async function FgetFloorplansByFolder (idProject,idFolder, data = [], hasMore = false, lastSyncedAt = null) {
const response = await axios.get(API_URL, {
params:{
'last_synced_at':lastSyncedAt
},
headers: {
'Authorization': API_TOKEN,
'Accept': 'application/json'
}
});
let XHasMore = response.headers['x-has-more'];
let lastSyncedAt = response.headers['x-last-synced-at'];
for(var i in response.data) {
if(response.data[i].folder_id != null || response.data[i].folder_id == idFolder){
data.push(response.data[i])
}
}
if(XHasMore == 'true'){
// 3. Return the result of the recursive call
return FgetFloorplansByFolder(idProject,idFolder, data, XHasMore, lastSyncedAt)
}
else {
console.log(data);
return data;
}
}

Promises with React [duplicate]

This question already has answers here:
What's the difference between returning value or Promise.resolve from then()
(6 answers)
Closed 4 years ago.
I have questions about promises. I'm just starting to deal with them and it's not that easy to understand!
I'm trying to setup an authentication system for my app.
RegisterPage
handleSubmit looks like that:
handleSubmit(event) {
event.preventDefault();
const { user } = this.state;
//some code here
userActions.register(user);
}
UserActions
function register(user) {
userService.register(user)
.then(
user => {
success(user);
},
error => {
failure(error.toString());
}
);
function success(user) { return { type: "REGISTER_SUCCESS", user } }
function failure(error) { return { type: "REGISTER_ERROR", error } }
}
UserService
function register(user) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user)
};
return fetch(`/api/users/register`, requestOptions).then(handleResponse);
}
function handleResponse(response) {
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok) {
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
}
Question 1. That code is "working" but not like I want. That way, even if the request success, I still can have error from the server, like duplicate username or something like that. I guess what I want is to return Promise.reject() not just if !response.ok but also if I have errors in the JSON returned right?
function handleResponse(response) {
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok) {
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
else if(data.errors) {
return Promise.reject(data.message);
}
return data;
});
}
Question 2. If everything's fine, should I return data or return Promise.resolve(data)? And why?
Checkout the documentation for fetch here: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful
It seems you should be using .catch() to get server errors and just use throw new Error() for having errors.
You don't really need to use Promise.resolve or Promise.reject.
To help refactor what you have, you can try this
function register(user) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user)
};
return fetch(`/api/users/register`, requestOptions).then(handleResponse);
}
function handleResponse(response) {
return response.text()
.then(text => {
if (!response.ok) {
const error = (data && data.message) || response.statusText;
throw new Error(error);
} else {
const data = text && JSON.parse(text);
return data;
}
})
.catch(error => throw new Error(err));
}

Calling an API as a non-blocking call JavaScript

I am building a todo-list like feature which adds a task when Enter is pressed on an input task field. The Enter calls an API (add Task) which takes approx 200ms to execute. Since this is blocking call it hinders my code to execute fully and affects the usability of my system. Here is a code example of what I am trying to achieve.
handleChange (event) {
if (e.key === 'Enter') {
targetTaskId = e.target.getAttribute("data-downlink")
this.props.addTask(this.props.currentProject.id, '', '', taskId, this.props.currentTasks) //this function calls an add Task API which halts my system momentarily
targetSelector = targetTaskId
$('#' + targetSelector).focus()
this.setState({activeTask: targetSelector})
highlightActiveComponent(targetTaskId)
}
}
//addTask
export function addTask (project_id, taskName, taskNotes, upLink, taskList) {
console.log('Add Task API call', project_id, taskName, taskNotes, upLink)
return (dispatch) => {
callApi('tasks?projectId=' + project_id + '&name=' + taskName + '&notes=' + taskNotes + '&upLink=' + upLink, 'post')
.then(res => {
console.log('Response new task ', res)
let newTask = {name: res.name, id: res.id, notes: res.notes, upLink: upLink, projectId: project_id, assignee: 0, completed: 0, tags: [], isLiked: false, stories: [], likes: [], downLink: res.downLink}
let newTaskList = addTaskToTaskList(taskList, upLink, newTask)
dispatch(updateTasks({currentTasks: newTaskList}))
dispatch({ type: 'SET_ACTIVE_TASK_ID', payload: res.id })
})
}
}
//Fetch
export const API_URL = 'https://clients.rohan.axcelmedia.ca/v1'
export default function callApi (endpoint, method = 'get', body) {
let headers = {
'Accept': 'application/json',
'Content-Type' : 'application/json'
}
if (auth.loggedIn()) {
headers = _.merge(headers, {
Authorization: `Bearer ${auth.getToken()}`
})
}
return fetch(`${API_URL}/${endpoint}`, {
headers,
method,
body: JSON.stringify(body)
}).then(response => {
return response
}).then(response => response.json().then(json => ({ json, response })))
.then(({ json, response }) => {
if (!response.ok) {
return Promise.reject(json)
}
return json
})
.then(
response => response,
error => error
)
}
Add Task to tasklist
export function addTaskToTaskList(tasks, upLink, newTask){
updateTaskDownLink(tasks, newTask.upLink, newTask.id)
updateTaskUpLink(tasks, newTask.downLink, newTask.id)
if(upLink == 0){
tasks.unshift(newTask)
// console.log("Added in the start", tasks)
return JSON.parse(JSON.stringify(tasks))
}
let myIndex = getIndexOfTaskById(tasks, upLink)
console.log("Added the new task from helper", myIndex)
if (myIndex) {
console.log("Added the new task")
tasks.splice(myIndex + 1, 0, newTask);
// console.log("New Task List", JSON.parse(JSON.stringify(tasks)))
}
return JSON.parse(JSON.stringify(tasks))
}
export function updateTaskUpLink(tasks, taskId, upLink){
tasks.forEach(function(element, index) {
if(element.id == taskId) { element.upLink = upLink }
});
return tasks
}
export function updateTaskDownLink(tasks, taskId, downLink){
tasks.forEach(function(element, index) {
if(element.id == taskId) { element.downLink = downLink }
});
return tasks
}
My question is, is there anyway to call this API in a non-blocking fashion so that my code continues to execute and when the response from the api is received my cursor moves to the new task in a seamless manner.
Any help would be appreciated. Thankyou
[EDIT] : Added fetch function to demonstrate the async calls
You should use something like Fetch API for call the API in a non-blocking way:
fetch("/api/v1/endpoint/5/", {
method: "get",
credentials: "same-origin",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}
}).then(function(response) {
return response.json();
}).then(function(data) {
console.log("Data is ok", data);
}).catch(function(ex) {
console.log("parsing failed", ex);
});
console.log("Ciao!");
The code that shows data in the snippet will be executed only when some data is returned by the server.
This means that in my example the log "Ciao!" will be showed before "Data is ok: ..."
Hope this helps :)
credits for the snippet: https://gist.github.com/marteinn/3785ff3c1a3745ae955c
First of all return JSON.parse(JSON.stringify(tasks)) is redundant, you can just return tasks right there, that will probably fix your speed problem alone. But incase it doesn't.
Your code might be blocking due to this type of thing here
tasks.forEach(function(element, index) {
if(element.id == taskId) { element.upLink = upLink }
});
return tasks
You iterate over the tasks array for updateTaskDownLink, again for updateTaskUpLink and probably again for getIndexOfTaskById, this is a lot of needless iteration.
Instead of searching through an array of tasks over and over, you should structure your tasks in a map
tasks = {
"someTaskId": {
id: "someTaskId",
upLink: "uplink stuff",
downLink: "downlink stuff"
}
}
This way when you go to update the task its really simple and really fast
tasks[taskId].upLink = upLink or tasks[taskId].downLink = downLink
No iterating, no blocking, no problem.
Also, this data structure will make getIndexOfTaskById obsolete! because you already have the key needed to access that task! Hooray!
If you're wondering how to iterate over your tasks structured as a map like that see here

Categories

Resources