Calling an API as a non-blocking call JavaScript - 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

Related

Adyen UPI client integration get status

I am somehow trying to get the status (error,success) after paying through the UPI app. I already end up in the onAdditionalDetails() function but here I somehow don't have the possibility to query the status. Is there maybe something needed to get this information in the state object?
async initAdyen_newurl() {
let config = null;
config = {
...this.config.adyenConfig,
onPaymentCompleted: (result, component) => {
console.info("onPaymentCompleted");
console.info(result, component);
},
onError: (error, component) => {
console.error("onError");
console.error(error.name, error.message, error.stack, component);
},
onAdditionalDetails: (state, component) => {
const actionUrl = "hardcoded for the moment"
const obj = {
paymentMethodType: component.props.paymentMethodType,
url: actionUrl,
method: "post",
type: "redirect",
paymentData: component.props.paymentData
}
component.props.createFromAction(obj, {}).mount("#id");
},
};
AdyenCheckout(config)
.then((checkout) => {
// init stuff
})
.catch((error) => {
console.error(`url failure ${error.message}`);
});
},
I can also redirect to the next page using createFromAction(), but this just happens in both Success and Error. However, this should only happen in Success. I hope that was somehow understandable. Many Thanks
edited: i am using version 5.23.0
The flow involves an additional step (3DS) so the onAdditionalDetails handler is invoked. From there you can add an extra call to /payments/details to fetch the payment status.
The response includes the resultCode to inform the shopper of the payment status.
Here is an example:
...
onPaymentCompleted: (result, component) => {
handleServerResponse(result, component);
},
onAdditionalDetails: async (response, _component) => {
// call server
const paymentDetailsResponse = await callServer("/api/paymentDetails", response);
// obtain payment status
const result = paymentDetailsResponse.resultCode
},
onError: (error, component) => {
console.error(error.name, error.message, error.stack, component);
}
// Calls your server endpoints
async function callServer(url, data) {
const res = await fetch(url, {
method: "POST",
body: data ? JSON.stringify(data) : "",
headers: {
"Content-Type": "application/json",
},
});
In the backend perform the paymentsDetails call to obtain the Payment status from the Adyen platform:
// Check payment result
app.post("/api/paymentDetails", async (req, res) => {
try {
const response = await checkout.paymentsDetails({
details: req.body.data.details,
paymentData: req.body.data.paymentData,
});
res.json(response);
} catch (err) {
console.error(`Error: ${err.message}, error code: ${err.errorCode}`);
res.status(err.statusCode).json(err.message);
}
});
See Confirm an additional action on your server

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!`)
}
}

ReactJS: Update values without reload the page

I have this problem, when I do a insert or a change about some data, to see the new data I need to reload the page while I would to update automatically the value without the need to reload the page. How can I do?
This is the part where the user click on submit and the post
_onSubmit(Document)
{
const self = this
if ( !_.isEmpty(Document) )
{
//..
if (Document && !_.isEmpty(Document.Anagraphics))
{
alertify.confirm(
utility.t('sureYouWanna_SAVE'),
() => {
const now = new Date();
Document._id = `PRODUCT:${new Date().getTime()}-${utility.CUID()}`
Document.CreationDate = now.toISOString()
Document.CategoryCode
Document.Status = 'New';
Document.Type = 'PRODUCT';
self._POST(Document)
},
function(){}
).set('labels', {ok: utility.t('YES_SAVE'), cancel: utility.t('CANCEL')})
}
else
{
$methods.WarnMissingValues()
}
}
else {
$methods.WarnMissingValues()
}
}
_POST(Document)
{
console.log("DOCUMENT POST", Document)
const self = this
const auth = this.props.db.auth
fetch(`${this.props.db.couch_db_host_url}requests`,{
method: 'POST',
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa(`${auth.username}:${auth.password}`)
},
body: JSON.stringify(Document)
})
.then(response => {
alertify.dismissAll()
if(response.status > 299 || response.status < 200){
alertify.error(utility.t('AN_ERROR_OCCURRED'))
self._updateState({ submitSucceeded: false })
}
else{
alertify.alert(utility.t('ITEM_EDITED_OK'), function(){})
self.props.history.push({
pathname: RoutesIT.products_details
})
}
})
.catch((err, warning) => {
if (err)
{
alertify.dismissAll()
alertify.error(utility.t('AN_ERROR_OCCURRED'))
console.log('_POST', err);
self._updateState({ submitSucceeded: false })
}
else
{
console.log(warning)
alertify.dismissAll()
alertify.warning(utility.t(warning))
}
})
}
How can I do to not reload the page to see the result of the post? Thank you
UPDATE:
In the page I have also:
function mapStateToProps(state) {
const { app: { login, p, c, l, c_timestamp, p_timestamp, l_timestamp }, form } = state;
return {
db: login ? login.db : null,
Sender: login ? login.Location : null,
timestamp: login ? login.timestamp : null,
[ FORM_NAME ]: form[FORM_NAME],
products: p,
locations: l,
categories: c,
categories_timestamp: c_timestamp,
products_timestamp: p_timestamp,
locations_timestamp: l_timestamp,
utente: login,
};
}
while the reducers
case actions.CATE_UPDATE:
{
return {
...state,
c: action.payload,
c_timestamp: new Date().getTime()
}
}
For what I can see in your code, the problem may lie in the fact that you're not dispatching any action when you submit the data.
Redux store can only be modified via actions, and since you're not triggering any, its contents are never being updated. This explains why your component is not updated in real time: your local data is never changing, so React is not aware of any updates. Things works when you reload the page because you're probably fetching the data from server, where the data did change during your POST request.
In order to fix this issue, you first need to pass a mapDispatchToProp to the your component, same as what you did with mapStateToProps:
connect(mapStateToProps, mapDispatchToProps)(YourComponent);
Inside of mapDispatchToProps, you have to return a property containing a function that will dispatch the CATE_UPDATE action you want to run:
const mapDispatchToProps = (dispatch) => ({
cateUpdateAction: (payload) => dispatch({
type: CATE_UPDATE,
payload
}),
});
Once you've done that, you'll be able to access this function from your component's props and call it inside of your _POST method.
if (response.status > 299 || response.status < 200){
alertify.error(utility.t('AN_ERROR_OCCURRED'))
self._updateState({ submitSucceeded: false })
} else {
alertify.alert(utility.t('ITEM_EDITED_OK'), function(){})
// Dispatch action to update data in Redux store
self.props.cateUpdateAction(data_to_save);
self.props.history.push({
pathname: RoutesIT.products_details
})
}

Unable to get fetch response on react native app

I am stuck on one of the mysterious issue. The problem goes like this:
What I Do??
Simply do login api call and if login success then I have to fetch amount of data from 5-6 api calls and store them in local database (Realm). Here is my code.
login(email, password) {
this.toggleLoadingFunction(true);
fetch(LoginURL, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
password: password,
request_from: 'mobile'
}),
})
.then(async res => {
if (res.ok) {
let data = await res.json();
global.user = data['user']
global.token = data['token']
getAllMasterDataAndSaveInRealm().then(() => {
this.toggleLoadingFunction(false);
global.storage.save({ key: 'LoggedInData', data: data });
this.props.navigation.navigate('Project', data);
}).catch(() => {
this.toggleLoadingFunction(false);
Alert.alert("Master Data Failed !!!");
})
} else {
this.toggleLoadingFunction(false);
let data = await res.json();
Alert.alert("Login Failed!!!", data.message)
}
})
.catch(error => {
this.toggleLoadingFunction(false);
Alert.alert("Network Error. Please try again.")
})
Here getAllMasterDataAndSaveInRealm() is lies on helper function which calls 5-6 apis and response back if all work is done. Here is how it looks like:
export const getAllMasterDataAndSaveInRealm = () => {
const token = global.token;
return new Promise.all([
getMaterials(token),
getEquipments(token),
getObjective(token),
getCategories(token),
getNcData(token),
getPlans(token)]
);
}
Each function inside getAllMasterDataAndSaveInRealm() returns Promise after successfully stored data in local realm db. Here is one of the above function.
export const getActivityPlan = (token) => {
return new Promise((resolve, reject) => {
return fetch(FetchActivityPlanDataURL, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'access_token': `${token}`
}
}).then((response) => {
console.log("Activity Plans Api response", response);
return response.json()
})
.then((responseJson) => {
const { data } = responseJson
console.warn("Activity Plans Api", data);
global.realm.write(() => {
for (var item of data) {
item.id = item.id ? item.id : 0;
item.activity_id = item.activity_id ? item.activity_id.toString() : "";
item.activity_name = item.activity_name ? item.activity_name.toString() : "";
item.activity_cost = item.activity_cost ? item.activity_cost.toString() : "";
item.project_id = item.project_id ? item.project_id : 0;
global.realm.create("ActivityPlan", item, true);
}
})
resolve(data);
})
.catch((error) => {
reject(`Activity Plan Failed ${error}`)
});
})
}
All remaining functions are same as above ( what they do is simply fetch data from api and store it in realm and resolve or reject)
What I Expect:
getAllMasterDataAndSaveInRealm() function Just store all the required data in db and let me know all done and then navigate to the another screen, as Login and fetching data is done.
Problem:
When I do run the app and process for login, Sometimes it works fine but most of the time App stuck on showing loader since some of the api call among 6 api from above do not get response from the request ( I do log the response) on wifi. But when I use mobile data and VPN it always works.
When I log request on server console, response is sent with code 200, but app is unable to get response for the request.
I am new on react native. I do lots of searches over internet but unable to find the solution. I don't have any idea whats going wrong with the code. Please help me out.
Project Configurations:
"react": "16.8.6",
"react-native": "0.60.4",
"realm": "^2.29.2",
Node version: v9.0.0

Redux Thunk - Change state before REST request

I am quite new to Redux Thunk and have an issue that I want to update a contract with a 'FileList' (file appendix), but if I use JSON.stringify the file will have a 0 value. If I convert the file to Base64 this problem is solved, but the PUT request is performed before the file is converted.
I searched a lot about Redux Thunk and think it might be some issue with Dispatch, I tried quite a lot and didn't become much wiser. Most of the things that I tried returned: "Actions must be plain objects. Use custom middleware for async actions."
Would appreciate some help or some search suggestions..
ps. contract.answers[0].answer[0] is the file. This acquires some refactoring, but first it needs to work.
const toBase64 = (file) => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
export function updateContract(contract) {
const base64File = toBase64(contract.answers[0].answer[0]);
base64File.then((value) => {
contract.answers[0].answer[0] = value; //Set file as base64
});
return {
type: SAVE,
fetchConfig: {
uri: contract._links.self,
method: 'PUT',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(contract), // Does not handle files
failureHandler(error) {
const {
details,
status,
} = error;
// If the contract was invalid, throw form errors:
if (status.code === 400 && details) {
// Map the question ids to fields:
throw new SubmissionError(Object.keys(details).reduce(
(acc, questionId) => {
acc[`question${questionId}`] = details[questionId];
return acc;
},
{},
));
}
return {
type: SAVE_FAILURE,
error,
};
},
successHandler(json) {
return {
type: SAVE_SUCCESS,
data: json,
};
},
},
};
}
Kind regards,
Gust de Backer
This happen because toBase64 return a Promise and itself is async, so in your case is necessary encapsule inside a new then.
export function updateContract(contract) {
const base64File = toBase64(contract.answers[0].answer[0]);
base64File.then((value) => {
contract.answers[0].answer[0] = value; //Set file as base64
});
return (dispatch) => {
base64File.then(() => dispatch({
type: SAVE,
fetchConfig: {
uri: contract._links.self,
method: 'PUT',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(contract), // Does not handle files
failureHandler(error) {
const {
details,
status,
} = error;
// If the contract was invalid, throw form errors:
if (status.code === 400 && details) {
// Map the question ids to fields:
throw new SubmissionError(Object.keys(details).reduce(
(acc, questionId) => {
acc[`question${questionId}`] = details[questionId];
return acc;
}, {},
));
}
return {
type: SAVE_FAILURE,
error,
};
},
successHandler(json) {
return {
type: SAVE_SUCCESS,
data: json,
};
},
},
}))
};
}
Yes, the redux accept a function as return, that function receive a dispatch on params, you can use it to dispatch the request after convert is ready :)

Categories

Resources