Recursive function in Redux - javascript

Trying to pass a reference to the recursive function to check if Redux action data fetch is complete, but getting function reference errors
const fetchAccountComplete = (state, accountNo) => { //state here is function reference
return new Promise(resolve => {
(function waitForFetchComplete(state, accountNo) {
const {isFetching, receivedAt} = state().account[accountNo] // getting state not a function here
if (!isFetching) return resolve()
setTimeout(waitForFetchComplete, 100)
})()
})
}
Is there a better way to return a promise to the caller function in Redux dispatch actions so that once the data is fetched, i need to do some other logic in other action.
Update 1:
should have been more clearer. There are two callers of this Request, Recieve actions on say Account data. First caller is directed similar to the above comment by you so waits until complete, second caller would not be doing the async call again and need to check if data fetch is complete so trying to see if recursive function with check on state so that promise can be resolved is being done

You could take advantage of promising chaining.
Example:
Have three actions like: IS_FETCHING, FETCH_SUCCESS, FETCH_ERROR.
IS_FETCHING:
Will simply set your state as pending (may be useful for showing a loading animation, for example).
FETCH_SUCCESS:
Will contain the result of the fetch to update the state. Will also clear the isUpdating flag
FETCH_ERROR:
Will contain any possible error due to the fetch (application or network error). Will also clear the isUpdating flag
Then, what you could do at application level is:
dispatch({type: IS_FETCHING, payload: data});
fetch(`https://MY-SERVER.com/?data=${data}`)
.then(response => response.json())
.then(json =>
dispatch({
type: isError(json) ? FETCH_RESULT : FETCH_ERROR,
payload: json
})
);
You could even benefit of action creators for the job.
Here is a good guide for that: https://redux.js.org/advanced/async-actions

If you have a function that returns a promise that is called multiple times with the same arguments then you can group that in a way so that the function is not called when it still has an unresolved promise and something tries to call it again with the same arguments.
Here is an example:
//group promise returning function
const createGroup = (cache) => (
fn,
getKey = (...x) => JSON.stringify(x)
) => (...args) => {
const key = getKey(args);
let result = cache.get(key);
if (result) {
return result;
}
//no cache
result = Promise.resolve(fn.apply(null, args)).then(
(r) => {
cache.done(key); //tell cache promise is done
return r;
},
(e) => {
cache.done(key); //tell cache promise is done
return Promise.reject(e);
}
);
cache.set(key, result);
return result;
};
//creates a cache that will remove cached value when
// Promise is done (resolved or rejected)
const createCache = (cache = new Map()) => {
return {
get: (key) => cache.get(key),
set: (key, value) => cache.set(key, value),
done: (key) => cache.delete(key),
};
};
//function that retuns a promise
const later = (time, value) => {
console.log('executing later with values', time, value);
return new Promise((r) =>
setTimeout(() => r(value), time)
);
};
//create group function with a cache that will remove
// cache key when promise is resolved or rejected
const groupAndRemoveCacheOnDone = createGroup(
createCache()
);
//grouped version of the later function
const groupedLater = groupAndRemoveCacheOnDone(later);
//testing the groped later
groupedLater(100, 8); //first call causes console.log
groupedLater(100, 8); //same arguments will not call later
groupedLater(100, 8); //will not call later
//will call later because arguments are not the same
// as the other calls
groupedLater(100, 'XX');
groupedLater(100, 8) //will not call later
.then((value) => {
console.log('resolved with:', value);
//this will call later because cache value is removed
// after promise is resolved
return groupedLater(100, 8);
})
.then(() => {
//testing with fetchAccountComplete
console.log(
'***** how many times is fetchAccountComplete called *****'
);
const fetchAccountComplete = (state, accountNo) => {
console.log(
'fetchAccountComplete called with',
accountNo
);
return new Promise((resolve) => {
(function waitForFetchComplete(state, accountNo) {
const {
isFetching,
receivedAt,
} = state().account[accountNo]; // getting state not a function here
if (!isFetching) return resolve();
setTimeout(
() => waitForFetchComplete(state, accountNo),
100
);
})(state, accountNo);
});
};
const data = {
account: [{ isFetching: true }],
};
const state = () => data;
const groupedFetchAccountComplete = groupAndRemoveCacheOnDone(
fetchAccountComplete
);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0).then((resolve) =>
console.log('resolved')
);
data.account[0].isFetching = false;
});

Related

ES6: Resolving Promise containing other Promise so that parent can use .then

I have a promise which contains another API caller promise containing resolver. Now when I want to use the .then for parent promise, I am not able to do it, error says Cannot read property 'then' of undefined, below is my sample code
const getData = () => dispatch => new Promise((resolve) => {
return apiService
.getByParameter(abc)
.then((data) => {
dispatch(update({
name: data.name
}));
resolve();
})
.catch(() => {
});
});
Now whenever I try to do
this.getData().then({
<--something-->
});
It throws ne error as Cannot read property 'then' of undefined
the method getByParamter comes from a Class, as
getByParameter(...params) {
const endpoint = `${this.getEndpoint.call(this, ...params)}`;
const timeInitiated = performance.now();
return request(() => axios.get(endpoint, extraHeaders), timeInitiated,
endpoint, ACTIONS.ACTION_GET);
}
const request = (rest, timeInitiated, endpoint, action) =>
new Promise((resolve, reject) => {
rest().then(({ data }) => {
const timeResolved = performance.now();
const timeCalculated = millisToMinutesAndSeconds(timeResolved - timeInitiated);
if (endpoint !== LOGS_ENDPOINT && timeCalculated > MAX_EXECUTION_TIME) {
apiLogger.warn(`The endpoint ${endpoint} took ${timeCalculated} seconds for ${action}`);
}
resolve(data);
})
.catch((response) => {
if (!isCancel(response)) {
reject(response);
} else {
apiLogger.debug('Request cancelled');
}
});
});
Please suggest what should be the solution to achieve what I need.
Your arrow function immediately, and unconditionally returns another function, not a promise!
const getData = () => (dispatch => new Promise(...))
getData() is a function, so .then does not exist on it.
Try it yourself
console.assert(typeof getData() !== "function", "`.then` doesn't exist on a function");
Honestly, this code ought to remove the dispatch callback and let the callee use a .then handler, that's what promises are for.
const getData = async () => {
const data = await apiService.getByParameter(abc);
return update(data);
});
getData returns a function that expects a dispatch paramter. If you call that function then you get a promise.
const dispatch = useDispatch();
const myPromise = this.getData()(dispatch);
Note the empty brakets in the last line followed by the call with dispatch as argument ()(dispatch)
In other words getData creates a thunk that you can use to create the promise.
const thunkFunction = getData();
const myPromise = thunkFunction(dispatch);
myPromise.then(...)

Resolving an array of promises in parallel, with error handling

I have a collection of promises or async functions, I need to manipulate slightly and then execute in parallel.
The issue I am having is that I am unable to resolve the promises.
// Some dummy tasks:
const taskA = () => Promise.resolve(500);
const taskB = () => {
return new Promise(resolve => resolve(300));
};
// Push them into an array:
const tasks = [];
const registerTask = (name, task) => {
tasks.push( async () => {
return { [name]: await task() };
});
};
// trying to get the results
const runTasks = () => {
const result = Promise.all(tasks).then(results => results);
return result;
}
// usage
registerTask('taskA', taskA);
registerTask('taskB', taskB);
console.log(runTasks())
Following the successful resolution of promises ideally I would also like to handle errors individually for each task.
The problem is that your registerTask function pushes functions onto the tasks array, not Promise objects. If you change the function like this, it should work:
const registerTask = (name, task) => {
const asyncFunc = async () => {
return { [name]: await task() };
};
tasks.push( asyncFunc() ); // This pushes a promise into 'tasks'
};
Your original code ended up with tasks being an array of functions that have not been called yet. When you call Promise.all(tasks) it ended immediately with undefined for each entry in the array because there were no Promise objects to wait on.
As for error handling, the Promise.all will fail with the first rejection or exception that happens. If you want to handle each task's individual error case, then you should do that in the Promise created in registerTask since doing it during the Promise.all is too late. It's hard to give an example since I'm not sure what kind of error handling would be appropriate for tasks, but perhaps something like this:
const registerTask = (name, task) => {
const asyncFunc = async () => {
return { [name]: await task() };
};
const promise = asyncFunc()
.catch(err => {
return handleError(name, err);
});
tasks.push( promise );
};
function handleError(name, err) {
console.log(`Task ${name} had error ${err}`);
// don't re-throw the error if you want all tasks to complete
// return some error object so you can see which tasks failed after they're done
return {error: err, name: name};
}
Promise.all expects an array of promises and you are passing an array of functions that returns a promise. So change your promise Promise.all to receive the promise of each function:
// Some dummy tasks:
const taskA = () => Promise.resolve(500);
const taskB = () => {
return new Promise(resolve => resolve(300));
};
// Push them into an array:
const tasks = [];
const registerTask = (name, task) => {
tasks.push( async () => {
return { [name]: await task() };
});
};
// trying to get the results
const runTasks = () => {
const result = Promise.all(tasks.map(task => task())); // Execute the tasks to receive the promises
return result;
}
// usage
registerTask('taskA', taskA);
registerTask('taskB', taskB);
runTasks().then((res) => {console.log(res)})

How to use promises instead of callback in reactjs?

i want to execute a function once the other function is completed. I have used callbacks but would like to use promises. But i am not sure how to go about it.
Below is the code,
this.set_function(this.save); //save is the callback method
set_function = (callback) => {
const some_var = {};
this.props.get_method.then(response => {
some_var.data = response;
this.setState({selected: some_var});
if(callback) {
callback(this.props.id, some_var_data);
}
});
};
save = (id, some_var) => {
const payload = {};
payload.some_var = [some_var];
client.update(id, payload)
.then((request) => {
this.save_data(id);
});
};
Here in the above code, once set_function is completed save function should be executed. As shown above it works with callback. How can i do the same with promises. Could someone help me with this?
The only trick there is that your callback expects two separate things (this.props.id and some_var_data). A promise can have only one fulfillment value — so you wrap those up as an object:
set_function = () => this.props.get_method.then(response => {
this.setState({selected: some_var});
return {id: this.props.id, data: response};
});
Notice that since you get a promise from this.props.get_method, we just chain off it.
(Your some_var_data was already an object, but it only had the data property, so I just included data in the result object directly.)
You'd use it like this:
set_function()
.then(({id, data}) => {
// use `id` and `data` here
})
.catch(error => {
// Handle error here
});
(Or don't include .catch and return the promise chain to something else that will handle errors.)
Or of course, if you used it in an async function:
const {id, data} = await set_function();
Make it promising, by returning the chained promise:
set_function = (callback) => {
return this.props.get_method.then(response => {
this.setState({selected: some_var});
return {id: this.props.id, some_var };
});
};
Then chain the other function:
this.set_function.then(this.save)
and finally desteucture the passed object:
save = ({ id, some_var }) => {
Await your promises
Using async ... await your function will return a promise that will resolve once your function is finished.
set_function = async () => {
const some_var = {};
const response = await this.props.get_method;
some_var.data = response;
this.setState({selected: some_var});
return [this.props.id, some_var_data];
};
When you call set_function it will return a promise so you can await or .then it. Like so:
this.set_function().then(this.save);
// where
save = ([ id, some_var ]) => {
...
}
In this case, you would have to have set_function return a Promise.
set_function = () => {
const some_var = {};
this.props.get_method.then(response => {
some_var.data = response;
this.setState({selected: some_var});
return Promise.resolve({id: this.props.id, some_var: some_var_data})
});
};
Now you can use it like this:
set_function().then(data => save(data)
Here's a jsfiddle you can play with.
https://jsfiddle.net/ctwLb3zf/

Async Callback Execution After setTimeout Problem

In my react app, I have a function that somewhat simulates a call to an API. Is there a way I can synchronously run a callback function after this API request so that my data comes back in the order I sent it?
// the order in the array here matters
const wordTypeArr = ['type1', 'type2', 'type3']
// loop sequentially through array
wordTypeArr.forEach((v,i) => {
getRandomWordFromAPI(v, addWord)
})
//simulated "API" - can't modify this function
const getRandomWordFromAPI = (type, callback) => {
return setTimeout(function() {
callback(
type in dictionary ?
sample(dictionary[type]) :
null
);
}, (Math.random() * 750 + 250));
}
//callback runs after response - update the state
const addWord = (val) => {
const newState = wordList
newState.push(val)
setWordList(newState);
}
As you can see, the getRandomWordFromAPI function returns a timeout function then executes the callback after the timeout asynchronously (out of order). This is undesirable, as my results must be in order.
Maybe I need to wrap addWord in a promise? or something similar?
Changing that function would indeed be preferred as it'll make the code simpler to read. But since you can send getRandomWordFromAPI a callback, you can send the resolve function as the callback instead of addWord() and then chain the addWord to the resolution.
I've put some parts of your code in comments, since we don't have the dictionary object and such, but the structure stays the same.
// the order in the array here matters
const wordTypeArr = ['type1', 'type2', 'type3'];
//simulated "API" - can't modify this function
const getRandomWordFromAPI = (type, callback) => {
return setTimeout(function() {
callback( type );
/*
callback(
type in dictionary ?
sample(dictionary[type]) :
null
);*/
}, (Math.random() * 750 + 250));
}
//callback runs after response - update the state
const addWord = (val) => {
console.log( val );
/*
const newState = wordList
newState.push(val)
setWordList(newState);
*/
}
const randomWords = wordTypeArr.map( word => {
return new Promise(( resolve, reject ) => {
getRandomWordFromAPI( word, resolve );
});
});
Promise.all( randomWords ).then( words => words.forEach( addWord ));
It's 2019, promises are in, callbacks are out. On a more serious note, here's how you can refactor your code to make it work the way you want to:
// mock the API
const getRandomWordFromAPI = (type, callback) => {
setTimeout(() => {
let word = `some-word-of-type-${type}`;
callback(word);
}, 1000)
}
// promisify the mocked API
const getRandomWordFromAPIPromise = (type) => new Promise(resolve => {
getRandomWordFromAPI(type, resolve);
});
// fetch all data asynchronously, in order
const wordTypeArr = ['type1', 'type2', 'type3'];
const promises = wordTypeArr.map(getRandomWordFromAPIPromise);
Promise.all(promises).then(words => {
// do whatever you want with the words
console.log(words)
});

React setState callback return value

I'm new in React and I was looking to achieve this kind of flow:
// set the state
// execute a function `f` (an async one, which returns a promise)
// set the state again
// return the promise value from the previous function
So, what I'm doing now is the following:
async function handleSomething() {
this.setState((prevState) => { ... },
() => {
let result = await f()
this.setState((prevState) => { ... },
...
)
})
return result;
}
Hope you get the idea of what I want to achieve. Basically I want to get result, which is the value returned from awaiting f, and return it in handleSomething so I can use it in another place, but wrapping it up inside those setState calls:
// g()
// setState
// res = f()
// setState
// return res
My question is, how can I do this properly? Maybe should I modify the state with the result value and get it from there?.
EDIT:
Usage of handleSomething:
// inside some async function
let result = await handleSomething()
You can create a Promise that resolves once both setState calls are done:
function handleSomething() {
return new Promise(resolve => {
this.setState(
prevState => {
/*...*/
},
async () => {
let result = await f();
this.setState(
prevState => {
/*...*/
},
() => resolve(result)
// ^^^^^^^ resolve the promise with the final result
);
}
);
});
}
Which would be used like:
this.handleSomething().then(result => /* ... */)
// or
const result = await this.handleSomething();

Categories