calling an api request multiple times sequentially using redux-saga - reactjs

I need to call an API request (with different request payloads) multiple times in my reactjs application. But, I don't want to call these multiple requests at the same time and I want to wait till one is completed and then another is called so that they are called sequentially. How can I do this using redux-saga?
Here is how I've impletemented my saga for this specific api, but I don't know how to modify it to work in the way I explained above:
export function* fetchChartData(action) {
const call = Effect.call;
const put = Effect.put;
try {
const response = yield call(chartApi.GetCharts, action.payload)
yield put({
type: ActionChartDataType.GET_CHART_DATA_SUCCESS,
payload: response.data.data
})
} catch (e) {
}
}
export function* getAllCharts() {
yield takeEvery(ActionChartDataType.GET_CHART_DATA_START, fetchChartData)
}
In fact, I need to have the action of GET_CHART_DATA_SUCCESS and then call the other GET_CHART_DATA_START. I really appreciate any help.

Here is an example of how to group a promise returning function in the way you describe:
const group = (promiseFunction) => {
let promise = Promise.resolve();
return (...args) => {
promise = promise
.catch(() => 'ignore')
.then(() => promiseFunction(...args));
return promise;
};
};
//a helper that returns a promise that resolves in one second
const later = (value) =>
new Promise((r) => setTimeout(() => r(value), 1000));
//promise returning function that returns passed argument a second later
const promiseFn = (value) => {
console.log('promiseFn called with', value);
return later(value).then((r) =>
r === 3 ? Promise.reject(r) : r
);
};
//grouped primiseFn
const groupedPromiseFn = group(promiseFn);
//test calling the grouped promise function
[1, 2, 3, 4, 5].forEach(
(value) =>
console.log('calling grouped with', value) ||
groupedPromiseFn(value).then(
(r) => console.log('resolved with:', r),
(r) => console.log('rejected with:', r)
)
);
You can use group to have chartApi.GetCharts behave in the way you want:
const groupedGetCharts = group(chartApi.GetCharts)
//later in your code:
const response = yield call(groupedGetCharts, action.payload)

Related

An array of promises is returned when using axios in map function?

I am mapping through this dummyAssests array and want to create a new array of objects based on the axios response called newAssetArray. But when I console.log the newAssetArray it just contains an array of Promises...
useEffect(() => {
let newAssetArray = dummyAssets.map(async function (asset) {
return await axios
.get(currrentPrice(asset.asset_id))
.then((response) => {
let cp = response.data.market_data.current_price.eur;
let value = Number(cp) * Number(asset.amount);
return { ...asset, value: +value };
})
.catch((error) => console.log(error.response.data.error));
});
setAssets(newAssetArray);
console.log(newAssetArray);
}, [dummyAssets]);
Console:
Yes, that is expected. Use Promise.all() to wait for all the promises to resolve. .map() itself is not promise aware so it does not "wait" for each individual iteration of its loop to resolve before going onto the next one. So, you have the result of calling N async functions which all return a promise as your .map() result. That is how the code is supposed to work. The await you are using it not accomplishing anything. If you use a for loop instead (which is promise-aware, then your loop will be sequenced.
Here's how you could use Promise.all() to get the results:
useEffect(() => {
Promise.all(dummyAssets.map(function(asset) {
return axios
.get(currrentPrice(asset.asset_id))
.then((response) => {
let cp = response.data.market_data.current_price.eur;
let value = Number(cp) * Number(asset.amount);
return { ...asset, value: +value };
}).catch((error) => {
console.log(error.response.data.error)
throw error;
});
})).then(newAssetArray => {
console.log(newAssetArray);
setAssets(newAssetArray);
}).catch(err => {
// do something to handle the error here
});
}, [dummyAssets]);
Or, you might find this implementation a bit simpler to follow:
useEffect(async () => {
try {
const newAssetArray = await Promise.all(dummyAssets.map(async function(asset) {
const response = await axios.get(currrentPrice(asset.asset_id));
const cp = response.data.market_data.current_price.eur;
const value = Number(cp) * Number(asset.amount);
return { ...asset, value: +value };
}));
console.log(newAssetArray);
setAssets(newAssetArray);
} catch (e) {
// do something to handle the error here
console.log(e);
}
}, [dummyAssets]);
Also, note that a structure such as:
async function someFunction() {
return await axios.get()
}
does nothing different than just:
async someFunction() {
return axios.get()
}
They both return a promise that resolves to whatever axios.get() resolves to. The first returns a promise because it's in an async function and ALL async functions return a promise at the point they hit the first await. The second returns a promise because you're directly returning the axios.get() promise. Either way, they both return a promise that resolves to the same thing.
So, in general return await fn() is not helping you vs just return fn() and is not recommended. Then, once you stop using await there, you don't need async for that .map() callback anymore either.

Data cannot be assigned to state in interval function in useEffect

Im getting data from axios async function and trying to assign to state in same function. When I print the values on console, i see that temporary value is not null but state is always null. when i rerender the page, state is not being null.
const [Pickup, setPickUp] = useState([]);
async function GetOrders() {
const result = await axios(
`EXAMPLEURL`,
);
setOrders(result.data);
var temp = [];
result.data.allOrders.forEach(element => {
if (element.order_type === 'PickupOrders') {
temp.push(element);
}
});
console.log(temp);
if (Pickup !== temp) {
setPickUp(temp);
}
}
useEffect(() => {
GetOrders();
const interval = setInterval(() => {
GetOrders();
console.log(Pickup);
}, 1000 * 5);
return () => clearInterval(interval)
}, []);
On console:
How can i fix this problem?
I assume you want to make a get request. Your axios function need to be completed such as ;
await axios
.get("YOUR URL", {
headers: // if you need to add header,
})
.then((response) =>{
setOrders(reponse.data);
})
.catch((error) => {
result = { errorMessage: error.message };
console.error('There was an error!', error);
});
return result;
Not completely sure what you're trying to achieve, but you can't compare Pickup !== temp this will be false all the time, you're comparing object references. Js will return all the time those values aren't equal.
This function GetOrders return a promise you don't need to use interval, you can use GetOrders.then(lambdaFunctionHere -> ());

How tu turn this function into a thunk react promise function (Updated)

I am fetching data from the blockchain. Contract adresses in this case.
Once I have the adresses I fetch some info on each specific adresses and add a key=>value pair to the object. This is all working and I'm getting all the right data. However, once in the component, the newly added key=>value pair is no longer present. I think this is because the value added is a promised and the dispatch is not waiting on it. How can I fix this so the dispatch it done only once the promised is resolved.
const tokenStream = await exchange.getPastEvents('OtherToken', {fromBlock:0, toBlock: 'latest'})
const allTokens = tokenStream.map((event) => event.returnValues)
console.log('ALL TOKEN DATA : ', allTokens)
allTokens.forEach( async element => {
let symbol = await exchange.methods.getERCsymbol(element.tokenAddress).call()
element.symbol = symbol
});
console.log('ALL TOKEN DATA AFTER : ',allTokens) // I see symbol
dispatch(allTokensLoaded(allTokens))
Better solution would be to use Promise.all to wait for multiple async request/promise to finish, and also you are mixing await and then, as your main function is already async you can write it in more neat and clean way using await only.
export const loadAllTokens = async (exchange, dispatch) => {
const result = await exchange.getPastEvents('OtherToken', {fromBlock:0, toBlock: 'latest'});
const allTokens = result.map((event) => event.returnValues);
await Promise.all(allTokens.map(async (element) => {
const innerResult = await exchange.methods.getERCsymbol(element.tokenAddress).call();
element.symbol = innerResult;
element[2]= innerResult;
}));
dispatch(allTokensLoaded(allTokens));
}
its more clean and better to understand :).
if any doubts please comment.
They happens to be nested async request and this is how I fixed it
export const loadAllTokens = async (exchange, dispatch) => {
await exchange.getPastEvents('OtherToken', {fromBlock:0, toBlock: 'latest'}).then( async(result) => {
const allTokens = result.map((event) => event.returnValues)
let count = 0
allTokens.forEach( async element => {
await exchange.methods.getERCsymbol(element.tokenAddress).call().then( async(result) =>{
element.symbol = result
element[2]= result
count += 1
}).then( () => {
if(count === allTokens.length){
dispatch(allTokensLoaded(allTokens))
}
})
})
})
}

React hooks, update after multiple promises execution for bulk processes

I am working on a component that reads a CSV file and sends a request per every line on the file. The process works great but I am trying to show feedback as the lines get successfully posted. The problem is that using the useState hook, the set function gets passed the moment on calling the function and not after each promise has been resolved. So I cannot append to the successful results array, the array keeps getting replaced with the last successful call.
The API calls are debounced by one second in order to prevent an overload to the server.
import React, {useState} from "react";
import CSVReader from "react-csv-reader";
import {post} from "../api";
function App() {
const [inserts, setInserts] = useState([])
const callApi = async (x) => {
let item = {
date: x.date,
value: x.value,
};
await post(`add-items`, item);
setInserts([...inserts, item])
};
const debouncedApiCall = (body, delay) => {
return new Promise((resolve) => {
const handler = () => callApi(body).then((x) => resolve(x));
setTimeout(handler, delay);
});
};
const insert = async (rows) => {
let timer = 0;
await Promise.all(
rows.map(async (x) => {
timer++;
return await debouncedApiCall(x, timer * 1000);
})
);
};
let onFileLoaded = (data) => {
insert(data).then((x) => console.log(x));
};
return (
<div>
<CSVReader onFileLoaded={onFileLoaded}/>
{JSON.stringify(inserts)}
</div>
);
}
export default App;
When your call API function is called, within its closure it captures the state of inserts. This means, that inserts is not always up to date. What you end up with is called a "stale closure".
To get around this, the mutation function provided by the useState method can accept a callback function. This callback function can recieve the latest state when calling the function. This is helpful in async operations.
your callApi function will become
const callApi = async (x) => {
let item = {
date: x.date,
value: x.value,
};
await post(`add-items`, item);
setInserts(prevState => [...prevState , item]) //prevState gets the latest state of inserts when setInserts is called
return (x); //will return this value once this async function finishes. similar to resolve(x)
};
I cant exactly debug your code, but I would think there is an unnecessary step. you should be able to change your insert function to await all your callApi's, and just return x from your callApi function (as I added above).
const insert = async (rows) => {
let timer = 0;
await Promise.all(
rows.map((x) => {
return callApi(x); //Promise.All wants an array of promises. async functions return a promise
})
);
};
As a side note, Promise.all returns a promise with the actual array results of all your promises results. You can fetch them by adding a .then to the Promise.All and remove the async from the insert function, or await the result.
Async Based: insert returns a promise, so you will need to handle that in the calling function.
const insert = async (rows) => {
let timer = 0;
const results = await Promise.all(
rows.map((x) => {
return callApi(x); //Promise.All wants an array of promises. async functions return a promise
})
);
return results; //array of all your x values for each row
};
None-async based: end of line, insert is the calling function
const insert = (rows) => {
let timer = 0;
Promise.all(
rows.map((x) => {
return callApi(x); //Promise.All wants an array of promises. async functions return a promise
})
).then((result) => {
//result is an array of all x values according to rows
});
};

using axios with promise API

I am using a promise based hook in a React app to fetch async data from an API.
I am also using a Axios, a promise based http client to call the API.
Is it an anti-pattern to use a promise based client inside another promise? The below code does not seem to work.
const getData = () => {
return new Promise((resolve, reject) => {
const url = "/getData";
axios.get(url)
.then(function(response) {
resolve(response);
})
.catch(function(error) {
reject(error);
});
});
const useAsync = (asyncFunction) => {
const [value, setValue] = useState(null);
const execute = useCallback(() => {
setPending(true);
setValue(null);
setError(null);
return asyncFunction()
.then(response => setValue(response))
.catch(error => setError(error))
.finally(() => setPending(false));
}, [asyncFunction]);
useEffect(() => {
execute();
}, [execute]);
return { execute, pending, value, error };
};
};
const RidesList = () => {
const {
pending,
value,
error,
} = useAsync(getData);
Oh man. I think you have a fundamental misunderstanding about how Promises work.
First, axios already returns a Promise by default. So your whole first function of getData can be reduced to:
const getData = () => {
const url = "/getData"
return axios.get(url)
}
But the meat of your code seems to indicate you want a querable Promise - so you can check the status of it for whatever reason. Here's an example of how you would do it, adapted from this snippet:
function statusPromiseMaker(promise) {
if (promise.isResolved) return promise
let status = {
pending: true,
rejected: false,
fulfilled: false
}
let result = promise.then(
resolvedValue => {
status.fulfilled = true
return resolvedValue
},
rejectedError => {
status.rejected = true
throw rejectedError
}
)
.finally(() => {
status.pending = false
})
result.status = () => status
return result
}
In this way, you can then do something like let thing = statusPromiseMaker(getData()) and if you look up thing.status.pending you'll get true or false etc...
I didn't actually run what's above, I may have forgotten a bracket or two, but hopefully this helps.
I have to admit - I haven't seen anything like this ever used in the wild. I am interested in knowing what you're actually trying to accomplish by this.
Axios itself returns a promise but if you want to make a custom class having your custom logic after each API call then you can use interceptors I was having the same requirement and this is how I am returning promises after applying my custom logic on each API call.
Interceptors will get executed separately after and before each request you made so we can simply use them if we want to modify our request or response.
here is my working solution have a look at it.
callApi = (method, endpoint, params) => {
this.apiHandler.interceptors.request.use((config) => {
config.method = method
config.url = config.baseURL + endpoint
config.params = params
return config
})
return new Promise((resolve, reject) => {
this.apiHandler.interceptors.response.use((config) => {
if (config.status == 200) {
resolve(config.data)
} else {
reject(config.status)
}
// return config
}, error => reject(error))
this.apiHandler()
})
}
Below is the code to call this function
helper.callApi("get", "wo/getAllWorkOrders").then(d => {
console.log(d)
})

Resources